Ticket #463 (reopened Defect)

Opened 6 months ago

Last modified 1 month ago

user activity detection partially inoperative on Linux

Reported by: fthomas Assigned to: davea
Priority: Major Milestone: 6.2
Component: Client - Daemon Version: 5.10.45
Keywords: Cc: fthomas

Description

Hi,

Since r13948 HOST_INFO::users_idle() in client/hostinfo_unix.C always returns true on Debian Linux and therefore the client doesn't suspend computation while the user is active (neither mouse nor keyboard activity is recognized). I compiled 5.10.27 with the previous HOST_INFO::users_idle() but all the #ifdefs removed and the resulting client suspends computation when the keyboard is used but doesn't suspend when the mouse is used. I thought this is because there is no /dev/mouse on my system, only /dev/input/mice and /dev/input/mouse0, but using both device files in HOST_INFO::users_idle() doesn't get computation suspending working when the mouse is used.

Grüße,
Frank

My global_prefs_override.xml contains the following relevant settings:

<global_preferences>
  ...
  <run_if_user_active>0</run_if_user_active>
  <idle_time_to_run>1.000000</idle_time_to_run>
  ...
</global_preferences>

Attachments

boinc-idlefix.patch (4.1 kB) - added by oteodoro on 01/04/08 22:05:18.
alternative mouse idle detection for linux clients
interrupts_idle.patch (1.7 kB) - added by fthomas on 04/14/08 05:20:29.
Patch to integrate interrupts_idle() into client/hostinfo_unix.C.

Change History

11/08/07 03:01:07 changed by fthomas

BTW: The part about not detecting mouse activity was also reported in #71 and #359. Ticket #71 was closed because of r13948, but as I said above this change does not fix the issue with broken mouse activity detection. Maybe #71 should be reopened?

12/02/07 06:53:39 changed by fthomas

Alex Malinovich confirmed independently from me that r13948 broke keyboard detection on Debian GNU/Linux, see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=448982#26.

12/02/07 07:37:25 changed by fthomas

I've just tested the official 5.10.28 Development version (Ubuntu Release - standard GUI) build from boinc.berkeley.edu and as expected it also does not detect keyboard and mouse activity on Debian GNU/Linux.

12/02/07 18:27:34 changed by davea

  • status changed from new to closed.
  • resolution set to fixed.

(In [14346]) - client: hopefully fix keyboard input detection on Linux

(may fix #463)

12/03/07 03:14:54 changed by fthomas

  • status changed from closed to reopened.
  • resolution deleted.

I've applied r14346 to 5.10.30 and built it, but keyboard input detection is still broken.

12/25/07 20:14:34 changed by davea

I don't have a desktop Debian system to test on; Frank, would you be able to debug this? (it should be something fairly simple)

(follow-up: ↓ 13 ) 12/30/07 05:08:43 changed by fthomas

Ok, here is what I found out. Negating check_all_logins results in the client suspending computation when there is activity on a terminal:

--- a/client/hostinfo_unix.C
+++ b/client/hostinfo_unix.C
@@ -965,7 +965,7 @@ bool HOST_INFO::users_idle(bool check_all_logins, double idl
     time_t idle_time = time(0) - (long) (60 * idle_time_to_run);

 #ifdef HAVE_UTMP_H
-    if (check_all_logins) {
+    if (!check_all_logins) {
         if (!all_logins_idle(idle_time)) return false;
     }
 #endif

Where activity on a terminal means either opening a terminal or typing something into a terminal. The client does not suspend when there is general keyboard activity, typing a URL into FireFox's location bar for example. With this change boinc behaves as before r13948. Was the check_all_logins parameter in function calls of users_idle() changed after r13948?

The device_idle(idle_time, "/dev/mouse") and device_idle(idle_time, "/dev/kbd") calls always return true on Debian GNU/Linux because there are no such device files (as I said in my initial report). They would be useless even if the device is changed to /dev/input/mouse0 (which exists on Debian) because its atime (time of last access) is always the time as the system booted up. My /dev is handled by udev, I don't know if the device's atime is updated, when udev is not used.

BTW: All tests were performed with the current code in trunk/boinc/.

12/30/07 14:45:24 changed by davea

  • status changed from reopened to closed.
  • resolution set to fixed.

(In [14449]) - Partly fix keyboard detection on Linux.

With this change, we detect opening a terminal or typing into a terminal; we don't detect typing into other applications. (from Frank Thomas). Partly fixes #463

01/04/08 22:05:18 changed by oteodoro

  • attachment boinc-idlefix.patch added.

alternative mouse idle detection for linux clients

01/04/08 22:05:51 changed by oteodoro

On my computer, Boinc segfaults on me when i pass --check_all_logins to boinc_client. The mouse fails to work also when I unpatched parts of the r13948 code. I created a patch for Linux to detect input mouse events from /dev/input/mouse0 which requires the CONFIG_INPUT_EVDEV kernel option. As of now, with this patch, both my keyboard and mouse events kill the project process and also the segfault gets avoided. It was patched against 5.8.28.

Tests that produced segfaults: 6.1.5 (trunk) fail, 5.10.29 fail, 5.10.28 fail, 5.10.27 fail, 5.10.26 good, 5.10.25 good, 5.10.21 good

01/04/08 22:06:44 changed by oteodoro

i mean 5.10.28

01/04/08 22:11:29 changed by oteodoro

the attachment didn't put the headers :( ill insert here

--- client/client_state.C.orig	2008-01-04 21:03:14.000000000 -0800
+++ client/client_state.C	2008-01-04 20:53:06.000000000 -0800
@@ -34,6 +34,10 @@
 #endif
 #endif
 
+#ifdef linux
+#include <sys/fcntl.h>
+#endif
+
 #include "parse.h"
 #include "str_util.h"
 #include "util.h"
@@ -125,6 +129,19 @@
     launched_by_manager = false;
     initialized = false;
     last_wakeup_time = dtime();
+#ifdef linux
+    mouse_moved = true;
+    mouse_event_t = time(NULL);
+    mouse_fd = open("/dev/input/mouse0", O_RDONLY);
+#endif    
+}
+
+CLIENT_STATE::~CLIENT_STATE()
+{
+#ifdef linux
+    if(mouse_fd != -1)
+        close(mouse_fd);
+#endif
 }
 
 void CLIENT_STATE::show_host_info() {
@@ -414,6 +431,10 @@
 		http_ops->get_fdset(curl_fds);
         all_fds = curl_fds;
         gui_rpcs.get_fdset(gui_rpc_fds, all_fds);
+#ifdef linux
+        FD_SET(mouse_fd, &all_fds.read_fds);
+        if (mouse_fd > all_fds.max_fd) all_fds.max_fd = mouse_fd;
+#endif        
         double_to_timeval(x, tv);
         n = select(
             all_fds.max_fd+1,
@@ -429,6 +451,17 @@
 
         http_ops->got_select(all_fds, x);
         gui_rpcs.got_select(all_fds);
+#ifdef linux
+        if (mouse_fd != -1) {
+            if (FD_ISSET(mouse_fd, &all_fds.read_fds)) {
+                char ps2_packet[3]; //assume ps/2 mouse protocol 3 byte packet
+                if(read(mouse_fd, ps2_packet, 3) == 3) {
+                    mouse_moved = true;
+                    mouse_event_t = time(NULL);
+                }
+            }
+        }
+#endif
 
         if (n==0) break;
 
@@ -496,6 +529,10 @@
 #ifdef __APPLE__
          , &idletime
 #endif
+#ifdef linux
+         , &mouse_moved
+         , mouse_event_t
+#endif
     );
 
     if (user_active != old_user_active) {
--- client/client_state.h.orig	2008-01-04 21:03:14.000000000 -0800
+++ client/client_state.h	2008-01-04 19:58:12.000000000 -0800
@@ -213,6 +213,7 @@
 // --------------- client_state.C:
 public:
     CLIENT_STATE();
+    ~CLIENT_STATE();    
     void show_host_info();
     int init();
     bool poll_slow_events();
@@ -243,6 +244,11 @@
     bool garbage_collect_always();
     bool update_results();
     int nresults_for_project(PROJECT*);
+#ifdef linux
+    int mouse_fd;
+    bool mouse_moved;
+    time_t mouse_event_t;
+#endif
 
 // --------------- cpu_sched.C:
 private:
--- client/hostinfo_unix.C.orig	2008-01-04 21:03:14.000000000 -0800
+++ client/hostinfo_unix.C	2008-01-04 19:58:12.000000000 -0800
@@ -946,6 +946,24 @@
     return (idleTime > (60 * idle_time_to_run));
 }
 
+#elif linux
+
+bool HOST_INFO::users_idle(bool check_all_logins, double idle_time_to_run, bool* mouse_moved, time_t mouse_event_t) {
+    time_t cur_time = time(NULL);
+    time_t idle_time = cur_time - (long) (60 * idle_time_to_run);
+    if (mouse_event_t != 0) {
+        if (mouse_moved && mouse_event_t < idle_time)
+            *mouse_moved = false;
+    }
+
+    bool idle_result = true;
+#ifdef HAVE_UTMP_H
+    idle_result = idle_result && all_logins_idle(idle_time);
+#endif
+    idle_result = idle_result && (mouse_moved ? !*mouse_moved : true);
+    return idle_result;
+}
+
 #else  // ! __APPLE__
 
 bool HOST_INFO::users_idle(bool check_all_logins, double idle_time_to_run) {
--- lib/hostinfo.h.orig	2008-01-04 21:03:21.000000000 -0800
+++ lib/hostinfo.h	2008-01-04 19:58:12.000000000 -0800
@@ -72,6 +72,8 @@
     bool host_is_running_on_batteries();
 #ifdef __APPLE__
     bool users_idle(bool check_all_logins, double idle_time_to_run, double *actual_idle_time=NULL);
+#elif linux
+    bool users_idle(bool check_all_logins, double idle_time_to_run, bool* mouse_moved = NULL, time_t mouse_event_t = 0);
 #else
     bool users_idle(bool check_all_logins, double idle_time_to_run);
 #endif
--- checkin_notes.orig	2008-01-04 21:03:32.000000000 -0800
+++ checkin_notes	2008-01-04 19:58:12.000000000 -0800
@@ -7985,3 +7985,13 @@
     mac_installer/
         release_boinc.sh
         release_GridRepublic.sh
+
+Orson Teodoro 04 Jan 2008
+    - Linux: Input event mouse idle detection and segfault fix
+    client/
+        client_state.C
+        client_state.h
+        hostinfo_unix.C
+        main.C
+    lib/
+        hostinfo.h

01/08/08 14:40:05 changed by davea

Ideally I'd like a simpler/smaller fix (e.g., not involving changes to client_state.C).

I think the same thing could be done entirely within one function, using a static fd variable, and using non-blocking I/O instead of select()

(in reply to: ↑ 7 ) 01/14/08 12:19:48 changed by fthomas

Replying to fthomas (myself):

Ok, here is what I found out. Negating check_all_logins results in the client suspending computation when there is activity on a terminal:

...

With this change boinc behaves as before r13948. Was the check_all_logins parameter in function calls of users_idle() changed after r13948?

I just found out that the client has the --check_all_logins option which sets the boolean CLIENT_STATE::check_all_logins to true if the client is started with it. Now if one starts the client with --check_all_logins activity detection will fail because of r14449. Instead of applying r14449 the BOINC client should be rather started with the --check_all_logins option, right? But what I don't understand is why activity detection worked before r13948 and without the --check_all_logins option, because the assignment of CLIENT_STATE::check_all_logins did not changed. Probably the use of --check_all_logins prior to r13948 also broke activity detection.

04/09/08 07:49:26 changed by fthomas

I've just toyed with Linux event interface to check for user input (from any input device) and this code snippet is the result:

#include <cstdio>
#include <ctime>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <linux/input.h>

#define EVENT_DEVICES 32
static int fd[EVENT_DEVICES];
static time_t last_event = time(NULL);

void open_event_devs() {
    char event_dev[32];

    for (int i = 0; i < EVENT_DEVICES; i++) {
        sprintf(event_dev, "/dev/input/event%i", i);
        fd[i] = open(event_dev, O_RDONLY|O_NONBLOCK);
    }
}

bool event_devs_idle(time_t t) {
    struct input_event ev;
    bool retval = true;

    if (last_event > t) return false;

    for (int i = 0; i < EVENT_DEVICES; i++) {
        if (fd[i] == -1)
            continue;
        if (read(fd[i], &ev, sizeof(struct input_event)) < 0)
            continue;
        if (ev.time.tv_sec > t) {
            last_event = ev.time.tv_sec;
            retval = false;
        }
    }
    return retval;
}

int main(int argc, char** argv) {
    open_event_devs();

    while (1) {
        time_t idle_time = time(NULL) - 3;
        if (event_devs_idle(idle_time))
            printf("OOO System is idle...\n");
        else
            printf("XXX User is active...\n");
        usleep(500000);
    }
    return 0;
}

This seems to work but the problem with /dev/input/event%i is that they are normally only readable by root (as well as other files in /dev/input/). So probably my code example or oteodoro's patch are NOT applicable to solve this problem.

BTW: Some documentation about the Linux event interface is available here:

http://www.frogmouth.net/hid-doco/x401.html http://www.frogmouth.net/hid-doco/c537.html

04/11/08 13:48:51 changed by Ageless

  • status changed from closed to reopened.
  • resolution deleted.

04/11/08 13:51:18 changed by Ageless

  • version set to 5.10.45.
  • milestone changed from 5.10 to 6.2.

Reopening to allow others to add comments, if necessary. This thread on the BOINC Dev forums shows it isn't working in 5.10.45

I'll update the milestone as well. But is there any solution in sight before the release of BOINC 6.2?

04/11/08 16:39:05 changed by Dagorath

It isn't just Debian, it's broke on Fedora 8/7/5 too.

<rant> I can appreciate the fact that volunteers working on this problem have families and jobs as their top priority. All of us appreciate the tremendous effort you volunteers put forth and wish we had the skills/knowledge to help out. I hope this problem can be given a little more attention than it has been given because it seems a lot of people are turning to Linux as Vista proves to be just another M$ scam.

BOINC does a pretty decent job of living off spare CPU cycles but not always, probably due to badly designed/written science apps, etc. Therefore we have a backup... BOINC can be configured to suspend apps on keyboard/mouse activity. That backup absolutely must work on all platforms or BOINC will acquire the reputation for being a resource hog. Users will not overlook that sin and software reviewers and bloggers will spread the word if the problem continues.

My Fedora 8 and 5 hosts are at the disposal of anybody who needs to test fixes for this problem. Contact me direct at dagorath at shaw dot ca if it helps. </rant>

04/14/08 04:07:43 changed by fthomas

Heureka! I think I've found a possible solution for this problem by reading the /proc/interrupts file and counting the interrupts of the keyboard, mouse or a PS/2 device (but not of an USB HID). If the mouse is moved or a keystroke happens the interrupts counter is increased and activity can then be detected. And since /proc/interrupts is normally world-readable, file permissions are also not a problem. Here is the code:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <unistd.h>

FILE* f;
long irq_count[256];

bool interrupts_idle(time_t t) {
    static time_t last_irq = time(NULL);
    char line[256];
    int i = 0;
    long ccount = 0;

    rewind(f);
    while (fgets(line, sizeof(line), f)) {
        // Check for mouse, keyboard and PS/2 devices.
        if (strcasestr(line, "mouse") != NULL ||
            strcasestr(line, "keyboard") != NULL ||
            strcasestr(line, "i8042") != NULL) {
            // If any IRQ count changed, update last_irq.
            if (sscanf(line, "%d: %ld", &i, &ccount) == 2 &&
                irq_count[i] != ccount) {
                last_irq = time(NULL);
                irq_count[i] = ccount;
            }
        }
    }
    return last_irq < t ? true : false;
}

int main(int argc, char** argv) {
    f = fopen("/proc/interrupts", "r");
    if (! f)
        exit(1);

    while (1) {
        time_t idle_time = time(NULL) - 3;
        if (interrupts_idle(idle_time))
            printf("OOO System is idle...\n");
        else
            printf("XXX User is active...\n");
        usleep(500000);
    }
    exit(0);
}

BTW: Can somebody (with the required permissions) please change the title of this ticket to "user activity detection partially inoperative on Linux", thanks.

04/14/08 05:20:29 changed by fthomas

  • attachment interrupts_idle.patch added.

Patch to integrate interrupts_idle() into client/hostinfo_unix.C.

04/14/08 05:28:09 changed by fthomas

Ok, I've just added the file interrupts_idle.patch which adds interrupts_idle() to client/hostinfo_unix.C. It seems to work fine for me. David, can you please test it and maybe improve the code.

(follow-up: ↓ 23 ) 04/14/08 15:23:02 changed by davea

  • summary changed from user activity detection got broken with r13948 on Debian Linux to user activity detection partially inoperative on.

I checked in the interrupts_idle() code.

Should we get rid of the other checks (e.g., all_tty_idle(), device_idle())?

04/14/08 15:33:18 changed by Nicolas

[15049]: improved user idle checking on Linux

Nobody said it's completely fixed, though :)

04/14/08 15:33:33 changed by Nicolas

  • summary changed from user activity detection partially inoperative on to user activity detection partially inoperative on Linux.

Fix title.

(in reply to: ↑ 20 ) 04/15/08 02:59:43 changed by fthomas

Replying to davea:

I checked in the interrupts_idle() code.

Thanks. I noticed that other functions that read from files under /proc are guarded by "#if LINUX_LIKE_SYSTEM". Should the interrupts_idle() call be guarded by this too? I also want to note that interrupts_idle() will always return false when it is first called. That means that boinc_client after it has been started always assumes that the user is not idle.

Should we get rid of the other checks (e.g., all_tty_idle(), device_idle())?

At least all_logins_idle() and all_tty_idle() should stay because interrupts_idle() will not detect activity from a remote login.

The question is if there are other systems where device_idle() works but interrupts_idle() not. Do other Unix-like systems even have /proc/interrupts? (According to http://en.wikipedia.org/wiki/Procfs /proc/interrupts seems to be a Linux extension)


If this page is incomplete or incorrect, please edit it or add it to the wiki to-do list. To do this, you must be logged in; click Login or Register above.