From 4a9239f808b08cf391ded6052bab9cc499e4b505 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 3 Sep 2004 20:14:23 +0000 Subject: add CPU load limiter git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@176 fefdeb5f-60dc-0310-8127-8f9354f1896f --- polyp/Makefile.am | 14 +++- polyp/cpulimit-test.c | 84 +++++++++++++++++++++++ polyp/cpulimit.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++ polyp/cpulimit.h | 34 ++++++++++ polyp/main.c | 49 +++++++++++--- polyp/mainloop-signal.c | 43 ++++++------ polyp/sample.c | 11 +++ polyp/sample.h | 3 + polyp/util.c | 17 +++-- 9 files changed, 390 insertions(+), 40 deletions(-) create mode 100644 polyp/cpulimit-test.c create mode 100644 polyp/cpulimit.c create mode 100644 polyp/cpulimit.h (limited to 'polyp') 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 +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 } -- cgit