add CPU load limiter

git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@176 fefdeb5f-60dc-0310-8127-8f9354f1896f
This commit is contained in:
Lennart Poettering 2004-09-03 20:14:23 +00:00
parent c73a298f88
commit 4a9239f808
10 changed files with 392 additions and 40 deletions

View file

@ -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

View 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\

84
polyp/cpulimit-test.c Normal file
View file

@ -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);
}

175
polyp/cpulimit.c Normal file
View file

@ -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;
}
}

34
polyp/cpulimit.h Normal file
View file

@ -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

View 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);

View 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));
if ((r = read(signal_pipe[0], &sig, sizeof(sig))) < 0) {
if (errno == EAGAIN)
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;
}
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]);

View 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);
}

View 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

View 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
}