You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
hotplug branch -- higher CPU load for libusb backend compared to the hidraw backend #789
mcuee …/libusb/hidapi_test/test_pr783 v15.2.0 21:00 cat test_pr783.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int keep_running = 1;
void* hotplug_thread_func(void* arg) {
struct timespec ts;
// We lock once outside to simulate the contention/wait state
pthread_mutex_lock(&mutex);
while (keep_running) {
// Simulating the bug: Relative timespec (100ms)
ts.tv_sec = 0;
ts.tv_nsec = 100000000;
// If this returns EINVAL or ETIMEDOUT immediately,
// the while loop will spin as fast as possible.
pthread_cond_timedwait(&cond, &mutex, &ts);
}
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread_id;
if (pthread_create(&thread_id, NULL, hotplug_thread_func, NULL) != 0) {
return 1;
}
// Keep the main thread alive for 10 seconds to allow the script to sample
printf("Spinning for 10 seconds... check CPU usage now.\n");
sleep(10);
keep_running = 0;
pthread_join(thread_id, NULL);
return 0;
}
mcuee …/libusb/hidapi_test/test_pr783 v15.2.0 21:00 cat test.sh
#!/bin/bash
# Configuration
#TEST_SRC="test_pr783.c"
TEST_BIN="./test_pr783"
DURATION=5
echo "--- libusb/hidapi CPU Usage Verification ---"
# 1. Compile
#if ! gcc -Wall "$TEST_SRC" -o "$TEST_BIN" -pthread -lhidapi-hidraw; then
# echo "Error: Compilation failed."
# exit 1
#fi
# 2. Run in background
$TEST_BIN &
TEST_PID=$!
# Give the process a moment to initialize and start spinning
sleep 0.5
echo "Started test program with PID: $TEST_PID"
echo "Monitoring CPU usage..."
SUM_CPU=0
COUNT=0
for ((i=1; i<=DURATION; i++)); do
sleep 1
# Improved CPU extraction: 'pcpu=' hides the header row entirely
CPU=$(ps -p $TEST_PID -o pcpu= | xargs)
# Alternative sampling line for Ubuntu 26.04
#CPU=$(top -b -n 1 -p $TEST_PID | grep $TEST_PID | awk '{print $9}')
# Check if we actually got a number
if [[ -n "$CPU" ]]; then
echo "Sample $i: $CPU%"
SUM_CPU=$(echo "$SUM_CPU + $CPU" | bc)
COUNT=$((COUNT + 1))
else
echo "Sample $i: Process not found or CPU data unavailable."
fi
done
# 3. Cleanup
kill $TEST_PID 2>/dev/null
# 4. Results calculation
if [ $COUNT -gt 0 ]; then
AVERAGE_CPU=$(echo "scale=2; $SUM_CPU / $COUNT" | bc)
echo "---------------------------------------"
echo "Average CPU Load: $AVERAGE_CPU%"
if (( $(echo "$AVERAGE_CPU > 10.0" | bc -l) )); then
echo "RESULT: FAIL (High CPU detected - Bug is present)"
else
echo "RESULT: PASS (Low CPU detected - Fix is effective)"
fi
else
echo "Error: No samples collected. Check if the test program crashed."
fi
I believe the high CPU usage is caused by the test codes. But then there are obvious differences between the two backends. Just wondering if it is expected or not.
Quick follow-up: ran the 4th reproducer under WSL Ubuntu with #783 merged (1889e9f). Both backends idle at ~0.4–0.5 % CPU (tick-level samples from /proc/<pid>/stat over 10-second windows).
The 88 / 81 % numbers from the first reproducer were a broken test harness — Gemini's code passed a {0, 1e8} relative timespec to pthread_cond_timedwait, but that call takes an absolute timespec (since epoch), so the wait returned ETIMEDOUT immediately and the thread spun. Literally the bug #783 fixed, reintroduced in the test. And neither of the first two reproducers actually linked into hidapi's hotplug code path; they were just spinning in their own test thread. Only the 4th reproducer exercises the real code, and that one is already ~0 %.
There is a small architectural reason libusb runs a hair hotter than hidraw, though:
Both backends poll every 5 ms while idle — ~200 wake-ups/sec (libusb/hid.c:1275, linux/hid.c:1229).
Per strace -c, libusb's libusb_handle_events_timeout_completed averages ~100 µs per poll syscall; hidraw's raw select() on the udev netlink fd averages ~42 µs per pselect6. libusb has to lock its internal event-handling mutex and walk its pollfd / timer / transfer lists on every wake.
libusb also spawns its own netlink-listener thread once a hotplug callback is registered, so the libusb backend runs one more thread than hidraw. Thread snapshot while idle:
All tiny in absolute terms. If we want to push this closer to zero, the real improvement on both backends is replacing the 5 ms timeout with an eventfd that teardown writes to, then blocking indefinitely in select / poll / libusb's event handler. That cuts idle syscalls from ~200/sec to 0 and drops teardown latency from up-to-5 ms to sub-ms. Worth a separate follow-up issue IMO.
TL;DR: the fix works; the residual libusb vs. hidraw delta is libusb-side event-loop bookkeeping + its internal netlink thread, not a bug.
Measurement / analysis done with Claude (claude-opus-4.7) under WSL Ubuntu.
Activity
mcuee commentedon Apr 24, 2026
Test code generated by free Google Gemini.
mcuee commentedon Apr 24, 2026
I just use the Makefile from hidapitester for hidraw backend, and then make minor modification to use libusb backend.
With libusb backend
With hidraw backend.
mcuee commentedon Apr 24, 2026
I believe the high CPU usage is caused by the test codes. But then there are obvious differences between the two backends. Just wondering if it is expected or not.
mcuee commentedon Apr 24, 2026
Google Gemini did not quite agree though.
Anyway, the following test code from Google Gemini shows nearly 0 % CPU usage.
mcuee commentedon Apr 24, 2026
The above test codes do not really use hidapi though. Gemini is trying to simulate the logic.
Anyway, the following code use hidapi hotplug branch and no issues.
mcuee commentedon Apr 24, 2026
More practical test code.
Adding debug info to the libusb backend of the hotplug branch.
Runlog -- no issues.
Youw commentedon Apr 24, 2026
Quick follow-up: ran the 4th reproducer under WSL Ubuntu with #783 merged (1889e9f). Both backends idle at ~0.4–0.5 % CPU (tick-level samples from
/proc/<pid>/statover 10-second windows).The 88 / 81 % numbers from the first reproducer were a broken test harness — Gemini's code passed a
{0, 1e8}relativetimespectopthread_cond_timedwait, but that call takes an absolute timespec (since epoch), so the wait returnedETIMEDOUTimmediately and the thread spun. Literally the bug #783 fixed, reintroduced in the test. And neither of the first two reproducers actually linked into hidapi's hotplug code path; they were just spinning in their own test thread. Only the 4th reproducer exercises the real code, and that one is already ~0 %.There is a small architectural reason libusb runs a hair hotter than hidraw, though:
libusb/hid.c:1275,linux/hid.c:1229).strace -c, libusb'slibusb_handle_events_timeout_completedaverages ~100 µs perpollsyscall; hidraw's rawselect()on the udev netlink fd averages ~42 µs perpselect6. libusb has to lock its internal event-handling mutex and walk its pollfd / timer / transfer lists on every wake.All tiny in absolute terms. If we want to push this closer to zero, the real improvement on both backends is replacing the 5 ms timeout with an
eventfdthat teardown writes to, then blocking indefinitely inselect/poll/ libusb's event handler. That cuts idle syscalls from ~200/sec to 0 and drops teardown latency from up-to-5 ms to sub-ms. Worth a separate follow-up issue IMO.TL;DR: the fix works; the residual libusb vs. hidraw delta is libusb-side event-loop bookkeeping + its internal netlink thread, not a bug.
Measurement / analysis done with Claude (claude-opus-4.7) under WSL Ubuntu.
mcuee commentedon Apr 24, 2026
Haha, free Google Gemini agrees.