add CPU load limiter
authorLennart Poettering <lennart@poettering.net>
Fri, 3 Sep 2004 20:14:23 +0000 (20:14 +0000)
committerLennart Poettering <lennart@poettering.net>
Fri, 3 Sep 2004 20:14:23 +0000 (20:14 +0000)
git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@176 fefdeb5f-60dc-0310-8127-8f9354f1896f

doc/todo
polyp/Makefile.am
polyp/cpulimit-test.c [new file with mode: 0644]
polyp/cpulimit.c [new file with mode: 0644]
polyp/cpulimit.h [new file with mode: 0644]
polyp/main.c
polyp/mainloop-signal.c
polyp/sample.c
polyp/sample.h
polyp/util.c

index 49e9a88..0da2a2e 100644 (file)
--- a/doc/todo
+++ b/doc/todo
@@ -16,6 +16,8 @@
 - automatic termination of daemon if unused
 - add sample directory
 - paman: show scache and sample size
+- add timing parameter to write callback of stream in client API
+- add option for disabling module loading
 
 ** later ***
 - xmlrpc/http
index 923a352..33b052f 100644 (file)
@@ -31,7 +31,7 @@ AM_LIBADD=$(PTHREAD_LIBS) -lm
 EXTRA_DIST = polypaudio.pa depmod.py esdcompat.sh.in
 bin_PROGRAMS = polypaudio pacat pactl
 bin_SCRIPTS = esdcompat.sh
-noinst_PROGRAMS = mainloop-test mainloop-test-glib mainloop-test-glib12 pacat-simple parec-simple 
+noinst_PROGRAMS = mainloop-test mainloop-test-glib mainloop-test-glib12 pacat-simple parec-simple cpulimit-test cpulimit-test2 
 
 polypconf_DATA=polypaudio.pa 
 
@@ -142,7 +142,8 @@ polypaudio_SOURCES = idxset.c idxset.h \
                xmalloc.c xmalloc.h \
                subscribe.h subscribe.c \
                debug.h \
-               sound-file-stream.c sound-file-stream.h
+               sound-file-stream.c sound-file-stream.h \
+               cpulimit.c cpulimit.h
 
 polypaudio_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS)
 polypaudio_INCLUDES = $(INCLTDL)
@@ -387,6 +388,15 @@ mainloop_test_glib12_SOURCES = $(mainloop_test_SOURCES)
 mainloop_test_glib12_CFLAGS = $(mainloop_test_CFLAGS) $(GLIB12_CFLAGS) -DGLIB_MAIN_LOOP
 mainloop_test_glib12_LDADD = $(mainloop_test_LDADD) $(GLIB12_LIBS) libpolyp-mainloop-glib12.la
 
+cpulimit_test_SOURCES = cpulimit-test.c cpulimit.c util.c
+cpulimit_test_CFLAGS = $(AM_CFLAGS)
+cpulimit_test_LDADD = $(AM_LDADD) libpolyp-mainloop.la
+
+cpulimit_test2_SOURCES = cpulimit-test.c cpulimit.c util.c
+cpulimit_test2_CFLAGS = $(AM_CFLAGS) -DTEST2
+cpulimit_test2_LDADD = $(AM_LDADD) libpolyp-mainloop.la
+
+
 if BUILD_LIBPOLYPCORE
 
 polypinclude_HEADERS+=cli-command.h\
diff --git a/polyp/cpulimit-test.c b/polyp/cpulimit-test.c
new file mode 100644 (file)
index 0000000..71c06ef
--- /dev/null
@@ -0,0 +1,84 @@
+/* $Id$ */
+
+/***
+  This file is part of polypaudio.
+  polypaudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published
+  by the Free Software Foundation; either version 2 of the License,
+  or (at your option) any later version.
+  polypaudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+  You should have received a copy of the GNU General Public License
+  along with polypaudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include <assert.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include "cpulimit.h"
+#include "mainloop.h"
+
+#ifdef TEST2
+#include "mainloop-signal.h"
+#endif
+
+static time_t start;
+
+#ifdef TEST2
+
+static void func(struct pa_mainloop_api *m, struct pa_signal_event *e, int sig, void *userdata) {
+    time_t now;
+    time(&now);
+    
+    if ((now - start) >= 30) {
+        m->quit(m, 1);
+        fprintf(stderr, "Test failed\n");
+    } else
+        raise(SIGUSR1);
+}
+
+#endif
+
+int main() {
+    struct pa_mainloop *m;
+    
+    m = pa_mainloop_new();
+    assert(m);
+
+    pa_cpu_limit_init(pa_mainloop_get_api(m));
+
+    time(&start);
+
+#ifdef TEST2
+    pa_signal_init(pa_mainloop_get_api(m));
+    pa_signal_new(SIGUSR1, func, NULL);
+    raise(SIGUSR1);
+    pa_mainloop_run(m, NULL);
+    pa_signal_done();
+#else
+    for (;;) {
+        time_t now;
+        time(&now);
+        
+        if ((now - start) >= 30) {
+            fprintf(stderr, "Test failed\n");
+            break;
+        }
+    }
+#endif
+
+    pa_cpu_limit_done();
+    
+    pa_mainloop_free(m);
+    
+}
diff --git a/polyp/cpulimit.c b/polyp/cpulimit.c
new file mode 100644 (file)
index 0000000..822e1f3
--- /dev/null
@@ -0,0 +1,175 @@
+/* $Id$ */
+
+/***
+  This file is part of polypaudio.
+  polypaudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published
+  by the Free Software Foundation; either version 2 of the License,
+  or (at your option) any later version.
+  polypaudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+  You should have received a copy of the GNU General Public License
+  along with polypaudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "cpulimit.h"
+#include "util.h"
+
+/* Utilize this much CPU time at most */
+#define CPUTIME_PERCENT 70
+
+#define CPUTIME_INTERVAL_SOFT (5)
+#define CPUTIME_INTERVAL_HARD (2)
+
+static time_t last_time = 0;
+static int the_pipe[2] = {-1, -1};
+static struct pa_mainloop_api *api = NULL;
+static struct pa_io_event *io_event = NULL;
+static struct sigaction sigaction_prev;
+static int installed = 0;
+
+static enum  {
+    PHASE_IDLE,
+    PHASE_SOFT
+} phase = PHASE_IDLE;
+
+static void reset_cpu_time(int t) {
+    int r;
+    long n;
+    struct rlimit rl;
+    struct rusage ru;
+
+    r = getrusage(RUSAGE_SELF, &ru);
+    assert(r >= 0);
+
+    n = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + t;
+
+    r = getrlimit(RLIMIT_CPU, &rl);
+    assert(r >= 0);
+
+    rl.rlim_cur = n;
+    r = setrlimit(RLIMIT_CPU, &rl);
+    assert(r >= 0);
+}
+
+static void write_err(const char *p) {
+    pa_loop_write(2, p, strlen(p));
+}
+
+static void signal_handler(int sig) {
+    assert(sig == SIGXCPU);
+
+    if (phase == PHASE_IDLE) {
+        time_t now;
+        char t[256];
+
+        time(&now);
+
+        snprintf(t, sizeof(t), "Using %0.1f%% CPU\n", (double)CPUTIME_INTERVAL_SOFT/(now-last_time)*100);
+        write_err(t);
+        
+        if (CPUTIME_INTERVAL_SOFT >= ((now-last_time)*(double)CPUTIME_PERCENT/100)) {
+            static const char c = 'X';
+
+            write_err("Soft CPU time limit exhausted, terminating.\n");
+            
+            /* Try a soft cleanup */
+            write(the_pipe[1], &c, sizeof(c));
+            phase = PHASE_SOFT;
+            reset_cpu_time(CPUTIME_INTERVAL_HARD);
+            
+        } else {
+
+            /* Everything's fine */
+            reset_cpu_time(CPUTIME_INTERVAL_SOFT);
+            last_time = now;
+        }
+        
+    } else if (phase == PHASE_SOFT) {
+        write_err("Hard CPU time limit exhausted, terminating forcibly.\n");
+        _exit(1);
+    }
+}
+
+static void callback(struct pa_mainloop_api*m, struct pa_io_event*e, int fd, enum pa_io_event_flags f, void *userdata) {
+    char c;
+    assert(m && e && f == PA_IO_EVENT_INPUT && e == io_event && fd == the_pipe[0]);
+    read(the_pipe[0], &c, sizeof(c));
+    m->quit(m, 1);
+}
+
+int pa_cpu_limit_init(struct pa_mainloop_api *m) {
+    int r;
+    struct sigaction sa;
+    assert(m && !api && !io_event && the_pipe[0] == -1 && the_pipe[1] == -1);
+    
+    time(&last_time);
+
+    if (pipe(the_pipe) < 0) {
+        fprintf(stderr, "pipe() failed: %s\n", strerror(errno));
+        return -1;
+    }
+
+    pa_make_nonblock_fd(the_pipe[0]);
+    pa_make_nonblock_fd(the_pipe[1]);
+    pa_fd_set_cloexec(the_pipe[0], 1);
+    pa_fd_set_cloexec(the_pipe[1], 1);
+
+    api = m;
+    io_event = api->io_new(m, the_pipe[0], PA_IO_EVENT_INPUT, callback, NULL);
+
+    phase = PHASE_IDLE;
+
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = signal_handler;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    
+    r = sigaction(SIGXCPU, &sa, &sigaction_prev);
+    assert(r >= 0);
+
+    installed = 1;
+
+    reset_cpu_time(CPUTIME_INTERVAL_SOFT);
+    
+    return 0;
+}
+
+void pa_cpu_limit_done(void) {
+    int r;
+
+    if (io_event) {
+        assert(api);
+        api->io_free(io_event);
+        io_event = NULL;
+        api = NULL;
+    }
+
+    if (the_pipe[0] >= 0)
+        close(the_pipe[0]);
+    if (the_pipe[1] >= 0)
+        close(the_pipe[1]);
+    the_pipe[0] = the_pipe[1] = -1;
+
+    if (installed) {
+        r = sigaction(SIGXCPU, &sigaction_prev, NULL);
+        assert(r >= 0);
+        installed = 0;
+    }
+}
diff --git a/polyp/cpulimit.h b/polyp/cpulimit.h
new file mode 100644 (file)
index 0000000..6d13b6e
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef foocpulimithfoo
+#define foocpulimithfoo
+
+/* $Id$ */
+
+/***
+  This file is part of polypaudio.
+  polypaudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published
+  by the Free Software Foundation; either version 2 of the License,
+  or (at your option) any later version.
+  polypaudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+  You should have received a copy of the GNU General Public License
+  along with polypaudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include "mainloop-api.h"
+
+/* This kills the polypaudio process if it eats more than 70% of the
+ * CPU time. This is build around setrlimit() and SIGXCPU. It is handy
+ * in case of using SCHED_FIFO which may freeze the whole machine  */
+
+int pa_cpu_limit_init(struct pa_mainloop_api *m);
+void pa_cpu_limit_done(void);
+
+#endif
index 87265da..eba15c3 100644 (file)
@@ -43,6 +43,7 @@
 #include "util.h"
 #include "sioman.h"
 #include "xmalloc.h"
+#include "cpulimit.h"
 
 static struct pa_mainloop *mainloop;
 
@@ -54,15 +55,37 @@ static void drop_root(void) {
     }
 }
 
-static void exit_signal_callback(struct pa_mainloop_api*m, struct pa_signal_event *e, int sig, void *userdata) {
-    m->quit(m, 1);
-    fprintf(stderr, __FILE__": got signal.\n");
+static const char* signal_name(int s) {
+    switch(s) {
+        case SIGINT: return "SIGINT";
+        case SIGTERM: return "SIGTERM";
+        case SIGUSR1: return "SIGUSR1";
+        case SIGUSR2: return "SIGUSR2";
+        case SIGXCPU: return "SIGXCPU";
+        case SIGPIPE: return "SIGPIPE";
+        default: return "UNKNOWN SIGNAL";
+    }
 }
 
-static void aux_signal_callback(struct pa_mainloop_api*m, struct pa_signal_event *e, int sig, void *userdata) {
-    struct pa_core *c = userdata;
-    assert(c);
-    pa_module_load(c, sig == SIGUSR1 ? "module-cli" : "module-cli-protocol-unix", NULL);
+static void signal_callback(struct pa_mainloop_api*m, struct pa_signal_event *e, int sig, void *userdata) {
+    fprintf(stderr, __FILE__": got signal %s.\n", signal_name(sig));
+
+    switch (sig) {
+        case SIGUSR1:
+            pa_module_load(userdata, "module-cli", NULL);
+            return;
+            
+        case SIGUSR2:
+            pa_module_load(userdata, "module-cli-protocol-unix", NULL);
+            return;
+        
+        case SIGINT:
+        case SIGTERM:
+        default:
+            fprintf(stderr, "Exiting.\n");
+            m->quit(m, 1);
+            return;
+    }
 }
 
 static void close_pipe(int p[2]) {
@@ -157,16 +180,19 @@ int main(int argc, char *argv[]) {
 
     r = pa_signal_init(pa_mainloop_get_api(mainloop));
     assert(r == 0);
-    pa_signal_new(SIGINT, exit_signal_callback, NULL);
-    pa_signal_new(SIGTERM, exit_signal_callback, NULL);
+    pa_signal_new(SIGINT, signal_callback, c);
+    pa_signal_new(SIGTERM, signal_callback, c);
     signal(SIGPIPE, SIG_IGN);
 
     c = pa_core_new(pa_mainloop_get_api(mainloop));
     assert(c);
     
-    pa_signal_new(SIGUSR1, aux_signal_callback, c);
-    pa_signal_new(SIGUSR2, aux_signal_callback, c);
+    pa_signal_new(SIGUSR1, signal_callback, c);
+    pa_signal_new(SIGUSR2, signal_callback, c);
 
+    r = pa_cpu_limit_init(pa_mainloop_get_api(mainloop));
+    assert(r == 0);
+    
     buf = pa_strbuf_new();
     assert(buf);
     r = pa_cli_command_execute(c, cmdline->cli_commands, buf, &cmdline->fail, &cmdline->verbose);
@@ -193,6 +219,7 @@ int main(int argc, char *argv[]) {
         
     pa_core_free(c);
 
+    pa_cpu_limit_done();
     pa_signal_done();
     pa_mainloop_free(mainloop);
     
index a16d845..4746837 100644 (file)
@@ -54,33 +54,31 @@ static void signal_handler(int sig) {
 }
 
 static void callback(struct pa_mainloop_api*a, struct pa_io_event*e, int fd, enum pa_io_event_flags f, void *userdata) {
+    ssize_t r;
+    int sig;
+    struct pa_signal_event*s;
     assert(a && e && f == PA_IO_EVENT_INPUT && e == io_event && fd == signal_pipe[0]);
 
-    for (;;) {
-        ssize_t r;
-        int sig;
-        struct pa_signal_event*s;
         
-        if ((r = read(signal_pipe[0], &sig, sizeof(sig))) < 0) {
-            if (errno == EAGAIN)
-                return;
-
-            fprintf(stderr, "signal.c: read(): %s\n", strerror(errno));
-            return;
-        }
-
-        if (r != sizeof(sig)) {
-            fprintf(stderr, "signal.c: short read()\n");
+    if ((r = read(signal_pipe[0], &sig, sizeof(sig))) < 0) {
+        if (errno == EAGAIN)
             return;
-        }
 
-        for (s = signals; s; s = s->next) 
-            if (s->sig == sig) {
-                assert(s->callback);
-                s->callback(a, s, sig, s->userdata);
-                break;
-            }
+        fprintf(stderr, "signal.c: read(): %s\n", strerror(errno));
+        return;
     }
+    
+    if (r != sizeof(sig)) {
+        fprintf(stderr, "signal.c: short read()\n");
+        return;
+    }
+    
+    for (s = signals; s; s = s->next) 
+        if (s->sig == sig) {
+            assert(s->callback);
+            s->callback(a, s, sig, s->userdata);
+            break;
+        }
 }
 
 int pa_signal_init(struct pa_mainloop_api *a) {
@@ -108,7 +106,8 @@ void pa_signal_done(void) {
     while (signals)
         pa_signal_free(signals);
 
-    api->io_free(io_event);
+
+        api->io_free(io_event);
     io_event = NULL;
 
     close(signal_pipe[0]);
index 6ec5600..747acf1 100644 (file)
@@ -119,3 +119,14 @@ double pa_volume_to_dB(pa_volume_t v) {
 
     return 20*log10((double) v/PA_VOLUME_NORM);
 }
+
+void pa_bytes_snprint(char *s, size_t l, off_t v) {
+    if (v >= 1024*1024*1024)
+        snprintf(s, l, "%0.1f GB", (double) v/1024/1024/1024);
+    else if (v >= 1024*1024)
+        snprintf(s, l, "%0.1f MB", (double) v/1024/1024);
+    else if (v >= 1024)
+        snprintf(s, l, "%0.1f KB", (double) v/1024);
+    else
+        snprintf(s, l, "%u B", (unsigned) v);
+}
index a547956..0141a7c 100644 (file)
@@ -116,6 +116,9 @@ double pa_volume_to_dB(pa_volume_t v);
 #define PA_DECIBEL_MININFTY (-200)
 #endif
 
+/** Pretty print a byte size value. (i.e. "2.5 MB") */
+void pa_bytes_snprint(char *s, size_t l, off_t v);
+
 PA_C_DECL_END
 
 #endif
index a3276fd..061d571 100644 (file)
@@ -223,16 +223,23 @@ void pa_raise_priority(void) {
         fprintf(stderr, __FILE__": setpriority() failed: %s\n", strerror(errno));
     else
         fprintf(stderr, __FILE__": Successfully gained nice level %i.\n", NICE_LEVEL);
-    
+
 #ifdef _POSIX_PRIORITY_SCHEDULING
     {
         struct sched_param sp;
-        sched_getparam(0, &sp);
+
+        if (sched_getparam(0, &sp) < 0) {
+            fprintf(stderr, __FILE__": sched_getparam() failed: %s\n", strerror(errno));
+            return;
+        }
+        
         sp.sched_priority = 1;
-        if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0)
+        if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0) {
             fprintf(stderr, __FILE__": sched_setscheduler() failed: %s\n", strerror(errno));
-        else
-            fprintf(stderr, __FILE__": Successfully gained SCHED_FIFO scheduling.\n");
+            return;
+        }
+
+        fprintf(stderr, __FILE__": Successfully enabled SCHED_FIFO scheduling.\n");
     }
 #endif
 }