summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/todo2
-rw-r--r--polyp/Makefile.am14
-rw-r--r--polyp/cpulimit-test.c84
-rw-r--r--polyp/cpulimit.c175
-rw-r--r--polyp/cpulimit.h34
-rw-r--r--polyp/main.c49
-rw-r--r--polyp/mainloop-signal.c43
-rw-r--r--polyp/sample.c11
-rw-r--r--polyp/sample.h3
-rw-r--r--polyp/util.c17
10 files changed, 392 insertions, 40 deletions
diff --git a/doc/todo b/doc/todo
index 49e9a88d..0da2a2e0 100644
--- 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
diff --git a/polyp/Makefile.am b/polyp/Makefile.am
index 923a3522..33b052f3 100644
--- a/polyp/Makefile.am
+++ b/polyp/Makefile.am
@@ -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
index 00000000..71c06ef7
--- /dev/null
+++ b/polyp/cpulimit-test.c
@@ -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
index 00000000..822e1f33
--- /dev/null
+++ b/polyp/cpulimit.c
@@ -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
index 00000000..6d13b6e5
--- /dev/null
+++ b/polyp/cpulimit.h
@@ -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
diff --git a/polyp/main.c b/polyp/main.c
index 87265da6..eba15c35 100644
--- a/polyp/main.c
+++ b/polyp/main.c
@@ -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);
diff --git a/polyp/mainloop-signal.c b/polyp/mainloop-signal.c
index a16d8457..4746837b 100644
--- a/polyp/mainloop-signal.c
+++ b/polyp/mainloop-signal.c
@@ -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]);
diff --git a/polyp/sample.c b/polyp/sample.c
index 6ec56000..747acf18 100644
--- a/polyp/sample.c
+++ b/polyp/sample.c
@@ -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);
+}
diff --git a/polyp/sample.h b/polyp/sample.h
index a5479562..0141a7cd 100644
--- a/polyp/sample.h
+++ b/polyp/sample.h
@@ -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
diff --git a/polyp/util.c b/polyp/util.c
index a3276fdf..061d5710 100644
--- a/polyp/util.c
+++ b/polyp/util.c
@@ -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
}