diff options
| author | Mattias Andrée <maandree@kth.se> | 2024-02-24 19:58:22 +0100 | 
|---|---|---|
| committer | Mattias Andrée <maandree@kth.se> | 2024-02-24 19:58:22 +0100 | 
| commit | 39ca33807b936a46bbd491ab5ecff817a6a28144 (patch) | |
| tree | dec4c1bfc788fa6efa242fd6398e9c4f080b6999 | |
| parent | First commit (diff) | |
| download | coreupdown-39ca33807b936a46bbd491ab5ecff817a6a28144.tar.gz coreupdown-39ca33807b936a46bbd491ab5ecff817a6a28144.tar.bz2 coreupdown-39ca33807b936a46bbd491ab5ecff817a6a28144.tar.xz | |
Add coreupdownd
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 20 | ||||
| -rw-r--r-- | TODO | 2 | ||||
| -rw-r--r-- | config.mk | 9 | ||||
| -rw-r--r-- | coredown.1 | 5 | ||||
| -rw-r--r-- | coreup.1 | 5 | ||||
| -rw-r--r-- | coreupdown.c | 313 | ||||
| -rw-r--r-- | coreupdownd.1 | 74 | 
8 files changed, 387 insertions, 42 deletions
| @@ -15,3 +15,4 @@  /coreupdown  /coreup  /coredown +/coreupdownd @@ -3,6 +3,16 @@  CONFIGFILE = config.mk  include $(CONFIGFILE) +CPPFLAGS_CONFS =\ +	-D'COREUP_THRESHOLD_CPU=$(COREUP_THRESHOLD_CPU)'\ +	-D'COREUP_THRESHOLD_TIME=$(COREUP_THRESHOLD_TIME)'\ +	-D'COREUP_COOLDOWN=$(COREUP_COOLDOWN)'\ +	-D'COREDOWN_THRESHOLD_CPU=$(COREDOWN_THRESHOLD_CPU)'\ +	-D'COREDOWN_THRESHOLD_TIME=$(COREDOWN_THRESHOLD_TIME)'\ +	-D'COREDOWN_COOLDOWN=$(COREDOWN_COOLDOWN)'\ +	-D'DAEMON_INTERVAL_SEC=$(DAEMON_INTERVAL_SEC)'\ +	-D'DAEMON_INTERVAL_NSEC=$(DAEMON_INTERVAL_NSEC)' +  OBJ =\  	coreupdown.o @@ -12,7 +22,7 @@ all: coreupdown  $(OBJ): $(HDR)  .c.o: -	$(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) +	$(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) $(CPPFLAGS_CONFS)  coreupdown: $(OBJ)  	$(CC) -o $@ $(OBJ) $(LDFLAGS) @@ -22,9 +32,11 @@ install: coreupdown  	mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1"  	test -f "$(DESTDIR)$(PREFIX)/bin/coreup" || test ! -e "$(DESTDIR)$(PREFIX)/bin/coreup"  	test -f "$(DESTDIR)$(PREFIX)/bin/coredown" || test ! -e "$(DESTDIR)$(PREFIX)/bin/coredown" +	test -f "$(DESTDIR)$(PREFIX)/bin/coreupdownd" || test ! -e "$(DESTDIR)$(PREFIX)/bin/coreupdownd"  	cp -- coreupdown "$(DESTDIR)$(PREFIX)/bin/coreup" -	ln -sf coreup "$(DESTDIR)$(PREFIX)/bin/coredown" -	cp -- coreup.1 coredown.1 "$(DESTDIR)$(MANPREFIX)/man1" +	ln -sf -- coreup "$(DESTDIR)$(PREFIX)/bin/coredown" +	ln -sf -- coreup "$(DESTDIR)$(PREFIX)/bin/coreupdownd" +	cp -- coreup.1 coredown.1 coreupdownd.1 "$(DESTDIR)$(MANPREFIX)/man1"  postinstall:  	chown '0:0' "$(DESTDIR)$(PREFIX)/bin/coreup" @@ -33,8 +45,10 @@ postinstall:  uninstall:  	-rm -f -- "$(DESTDIR)$(PREFIX)/bin/coreup"  	-rm -f -- "$(DESTDIR)$(PREFIX)/bin/coredown" +	-rm -f -- "$(DESTDIR)$(PREFIX)/bin/coreupdownd"  	-rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/coreup.1"  	-rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/coredown.1" +	-rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/coreupdownd.1"  clean:  	-rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda @@ -1,2 +0,0 @@ -Add coreupdownd to automatically run coreup and coredown when the -machine has high or low CPU usage @@ -1,6 +1,15 @@  PREFIX    = /usr  MANPREFIX = $(PREFIX)/share/man +COREUP_THRESHOLD_CPU = 95 +COREUP_THRESHOLD_TIME = 2 +COREUP_COOLDOWN = 4 +COREDOWN_THRESHOLD_CPU = 50 +COREDOWN_THRESHOLD_TIME = 2 +COREDOWN_COOLDOWN = 4 +DAEMON_INTERVAL_SEC = 1 +DAEMON_INTERVAL_NSEC = 0 +  CC = c99  CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE @@ -31,7 +31,7 @@ utility does not use the standard input.  None.  .SH ENVIRONMENT VARIABLES  No environment variables affects the execution of -.B coredown +.BR coredown .  .SH ASYNCHRONOUS EVENTS  Default.  .SH STDOUT @@ -75,4 +75,5 @@ None.  .SH FUTURE DIRECTIONS  None.  .SH SEE ALSO -.BR coreup (1) +.BR coreup (1), +.BR coreupdownd (1) @@ -25,7 +25,7 @@ utility does not use the standard input.  None.  .SH ENVIRONMENT VARIABLES  No environment variables affects the execution of -.B coreup +.BR coreup .  .SH ASYNCHRONOUS EVENTS  Default.  .SH STDOUT @@ -69,4 +69,5 @@ None.  .SH FUTURE DIRECTIONS  None.  .SH SEE ALSO -.BR coredown (1) +.BR coredown (1), +.BR coreupdownd (1) diff --git a/coreupdown.c b/coreupdown.c index 1de5007..403bdf6 100644 --- a/coreupdown.c +++ b/coreupdown.c @@ -3,37 +3,46 @@  #include <errno.h>  #include <fcntl.h>  #include <limits.h> +#include <stdint.h>  #include <stdio.h>  #include <stdlib.h>  #include <string.h> +#include <time.h>  #include <unistd.h> -int -main(int argc, char *argv[]) +static const char *argv0; + +#define coreup() coreupdown(ULONG_MAX) +#define coredown() coreupdown(1) + +static int +coreupdown(unsigned long int count)  { -	unsigned long i, count; -	const char *command, *argv0; -	char path[sizeof("/sys/devices/system/cpu/cpu/online") + 3 * sizeof(i)]; +	char path[sizeof("/sys/devices/system/cpu/cpu/online") + 3 * sizeof(count)]; +	unsigned long int i;  	int fd, ret = 0; -	if (argc == 0) { -	wrong_command: -		fprintf(stderr, "usage: coreup [count]\nusage: coredown [count]\n"); -		return 1; +	for (i = 1; i; i++) { +		sprintf(path, "/sys/devices/system/cpu/cpu%zu/online", i); +		fd = open(path, O_WRONLY); +		if (fd < 0) { +			if (errno == ENOENT) +				break; +			fprintf(stderr, "%s: open %s O_WRONLY\n", argv0, path); +			return -1; +		} +		if (write(fd, i < count ? "1\n" : "0\n", 2) < 0) { +			fprintf(stderr, "%s: write %s\n", argv0, path); +			ret = -1; +		} +		close(fd);  	} +	return ret; +} -	argv0 = *argv++; -	argc--; -	command = strrchr(argv0, '/'); -	command = command ? &command[1] : argv0; - -	if (!strcmp(command, "coreup")) -		count = ULONG_MAX; -	else if (!strcmp(command, "coredown")) -		count = 1; -	else -		goto wrong_command; - +static int +oneshot_main(int argc, char **argv, unsigned long int count) +{  	if (argc && !strcmp(argv[0], "--")) {  		argv++;  		argc--; @@ -45,7 +54,7 @@ main(int argc, char *argv[])  		return 1;  	} -	if (argc >= 1) { +	if (argc) {  		char *end;  		errno = 0;  		if (!isdigit(**argv)) @@ -57,21 +66,259 @@ main(int argc, char *argv[])  		argc--;  	} -	for (i = 1; i; i++) { -		sprintf(path, "/sys/devices/system/cpu/cpu%zu/online", i); -		fd = open(path, O_WRONLY); -		if (fd < 0) { -			if (errno == ENOENT) +	return -coreupdown(count); +} + +static int +getusage(uintmax_t *total_out, uintmax_t *idle_out, size_t *cpus_out) +{ +#define IDLE_FIELD 3 + +	static uintmax_t last_total = 0, last_idle = 0; +	uintmax_t cur_total, cur_idle; +	char buf[512]; +	int fd; +	size_t off, len, field; +	ssize_t r; +	int line_incomplete = 0, beginning_of_line = 1; +	uintmax_t amount[] = {0, 0}, value; + +	*cpus_out = 0; + +	fd = open("/proc/stat", O_RDONLY); +	if (fd < 0) { +		fprintf(stderr, "%s: open /proc/stat O_RDONLY\n", argv0); +		return -1; +	} + +	len = 0; +	for (;;) { +		r = read(fd, &buf[len], sizeof(buf) - len); +		if (!r) { +			fprintf(stderr, "%s: /proc/stat did not contain \"cpu\" line\n", argv0); +			close(fd); +			return -1; +		} else if (r < 0) { +			if (errno == EINTR) +				continue; +			fprintf(stderr, "%s: read /proc/stat: %s\n", argv0, strerror(errno)); +			close(fd); +			return -1; +		} +		len += (size_t)r; + +		if (line_incomplete) +			goto skip_line; + +		for (off = 0; off < len;) { +			if (len - off < 4) { +				memmove(&buf[0], &buf[off], len -= off);  				break; -			fprintf(stderr, "%s: open %s O_WRONLY\n", argv0, path); -			return 1; +			} else if (!strncmp(&buf[off], "cpu", 3)) { +				if (isspace(buf[off + 3])) +					goto cpu_line_found; +				else if (isdigit(buf[off + 3])) +					*cpus_out += 1; +			} + +		skip_line: +			while (off < len && buf[off] != '\n') +				off++; +			if (off == len) { +				line_incomplete = 0; +				break; +			} else { +				off++; +				line_incomplete = 1; +			}  		} -		if (write(fd, i < count ? "1\n" : "0\n", 2) < 0) { -			fprintf(stderr, "%s: write %s\n", argv0, path); -			ret = 1; +	} +cpu_line_found: + +	off += 4; +	field = 0; +	value = 0; +	for (;;) { +		if (off == len) { +			off = len = 0; +			r = read(fd, buf, sizeof(buf)); +			if (!r) { +				goto line_done; +			} else if (r < 0) { +				if (errno == EINTR) +					continue; +				fprintf(stderr, "%s: read /proc/stat: %s\n", argv0, strerror(errno)); +				close(fd); +				return -1; +			} +			len = (size_t)r;  		} +		while (off < len && isspace(buf[off])) +			off++; +		if (off == len) +			continue; +		while (off < len) { +			if (buf[off] == '\n') { +				amount[field++ == IDLE_FIELD] += value; +				off++; +				goto line_done; +			} else if (isspace(buf[off])) { +				amount[field++ == IDLE_FIELD] += value; +				value = 0; +				off++; +				break; +			} else { +				value *= 10; +				value += buf[off] & 15; +				off++; +			} +		} +	} +line_done: + +	for (;;) { +		if (off == len) { +			off = len = 0; +		read_more: +			r = read(fd, &buf[len], sizeof(buf) - len); +			if (!r) { +				break; +			} else if (r < 0) { +				if (errno == EINTR) +					continue; +				fprintf(stderr, "%s: read /proc/stat: %s\n", argv0, strerror(errno)); +				close(fd); +				return -1; +			} +			len = (size_t)r; +		} +		for (; off < len; off++) { +			if (beginning_of_line) { +				if (len - off < 5) { +					memmove(&buf[0], &buf[off], len -= off); +					goto read_more; +				} +				if (!strncmp(&buf[off], "cpu", 3) && isdigit(buf[off + 3])) +					*cpus_out += 1; +				beginning_of_line = 0; +			} +			if (buf[off] == '\n') +				beginning_of_line = 1; +		} +	} + +	close(fd); + +	if (field < IDLE_FIELD) { +		fprintf(stderr, "%s: /proc/stat did not contain CPU idle time\n", argv0);  		close(fd); +		return -1;  	} -	return ret; +	cur_total = amount[0] + amount[1]; +	cur_idle = amount[1]; +	*total_out = cur_total - last_total; +	*idle_out = cur_idle - last_idle; +	last_total = cur_total; +	last_idle = cur_idle; +	return 0; + +#undef IDLE_FIELD +} + +static int +daemon_main(int argc, char **argv) +{ +	uintmax_t total, idle; +	size_t cpus; +	int up_time = 0, down_time = 0, cooldown = 0; +	struct timespec interval, rem; + +	if (argc && !strcmp(argv[0], "--")) { +		argv++; +		argc--; +	} + +	if (argc > 0) { +		fprintf(stderr, "usage: %s\n", argv0); +		return 1; +	} + +	/* TODO configurations should be configurable */ + +	/* TODO add SIGHUP support */ + +	/* TODO add proper daemonisation */ + +	interval.tv_sec = DAEMON_INTERVAL_SEC; +	interval.tv_nsec = DAEMON_INTERVAL_NSEC; + +	if (getusage(&total, &idle, &cpus)) +		return 1; + +	close(STDIN_FILENO); +	close(STDOUT_FILENO); + +	for (;;) { +		if (clock_nanosleep(CLOCK_REALTIME /* = monotonic */, 0, &interval, &rem)) { +			do { +				if (errno != EINTR) { +					fprintf(stderr, "%s: clock_nanosleep CLOCK_REALTIME: %s\n", +					        argv0, strerror(errno)); +					return 1; +				} +			} while (clock_nanosleep(CLOCK_REALTIME, 0, &rem, &rem)); +		} +		sleep(1); +		if (getusage(&total, &idle, &cpus)) +			return 1; +		if (cooldown) { +			cooldown--; +			continue; +		} + +		if (cpus == 1 && 100 * (total - idle) > COREUP_THRESHOLD_CPU * total) { +			down_time = 0; +			if (up_time++ >= COREUP_THRESHOLD_TIME) { +				cooldown = COREUP_COOLDOWN; +				coreup(); +				up_time = 0; +			} +		} else if (cpus > 1 && 100 * (total - idle) < COREDOWN_THRESHOLD_CPU * total * cpus) { +			up_time = 0; +			if (down_time++ >= COREDOWN_THRESHOLD_TIME) { +				cooldown = COREDOWN_COOLDOWN; +				coredown(); +				down_time = 0; +			} +		} else { +			up_time = 0; +			down_time = 0; +		} +	} + +	return 0; +} + +int +main(int argc, char **argv) +{ +	const char *command; + +	if (argc--) { +		command = strrchr(argv0 = *argv++, '/'); +		command = command ? &command[1] : argv0; +		if (!strcmp(command, "coreup")) +			return oneshot_main(argc, argv, ULONG_MAX); +		if (!strcmp(command, "coredown")) +			return oneshot_main(argc, argv, 1); +		if (!strcmp(command, "coreupdownd")) +			return daemon_main(argc, argv); +	} + +	fprintf(stderr, "usage: coreup [count]\n" +	                "usage: coredown [count]\n" +	                "usage: coreupdownd\n"); + +	return 1;  } diff --git a/coreupdownd.1 b/coreupdownd.1 new file mode 100644 index 0000000..9dde00b --- /dev/null +++ b/coreupdownd.1 @@ -0,0 +1,74 @@ +.TH COREUPDOWND 1 COREUPDOWN +.SH MAME +coreupdownd - Dynamically enable and disable CPU' to only use one during low CPU usage +.SH SYNPOSIS +coreupdownd +.SH DESCRIPTION +The +.B coreupdownd +daemon dynamically turns all CPU cores, except except +the main one, on and off depending on CPU usage. +.PP +The daemon periodically determines what the CPU usage +would be if only the main CPU core was enable. If it +would be saturated, the other cores are enabled, otherwise +the they are disabled. +.PP +This is designed as a workaround for virtual machines +that have input performance issues (keyboard input lag +and keys getting stuck) when more than one CPU core is +online. +.SH OPTIONS +No options are supported. +.SH OPERANDS +No operands are supported. +.SH STDIN +The +.B coreupdownd +daemon does not use the standard input, however +it will be closed by the daemon once initialisation +has completed. +.SH INPUT FILES +None. +.SH ENVIRONMENT VARIABLES +No environment variables affects the execution of +.BR coreupdownd . +.SH ASYNCHRONOUS EVENTS +Default. +.SH STDOUT +The +.B coreupdownd +daemon does not use the standard output, however +it will be closed by the daemon once initialisation +has completed. +.SH STDERR +The standard error is used for diagnostic messages. +.SH OUTPUT FILES +None. +.SH EXTENDED DESCRIPTION +None. +.SH EXIT STATUS +If the +.B coreupdownd +daemon only terminates on failure, in which case +the exit status will be: +.TP +1 +An error occurred. +.SH CONSEQUENCES OF ERRORS +Default. +.SH APPLICATION USAGE +None. +.SH EXAMPLES +None. +.SH RATIONALE +None. +.SH NOTES +None. +.SH BUGS +None. +.SH FUTURE DIRECTIONS +None. +.SH SEE ALSO +.BR coreup (1), +.BR coredown (1) | 
