aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2024-02-24 19:58:22 +0100
committerMattias Andrée <maandree@kth.se>2024-02-24 19:58:22 +0100
commit39ca33807b936a46bbd491ab5ecff817a6a28144 (patch)
treedec4c1bfc788fa6efa242fd6398e9c4f080b6999
parentFirst commit (diff)
downloadcoreupdown-39ca33807b936a46bbd491ab5ecff817a6a28144.tar.gz
coreupdown-39ca33807b936a46bbd491ab5ecff817a6a28144.tar.bz2
coreupdown-39ca33807b936a46bbd491ab5ecff817a6a28144.tar.xz
Add coreupdownd
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--.gitignore1
-rw-r--r--Makefile20
-rw-r--r--TODO2
-rw-r--r--config.mk9
-rw-r--r--coredown.15
-rw-r--r--coreup.15
-rw-r--r--coreupdown.c313
-rw-r--r--coreupdownd.174
8 files changed, 387 insertions, 42 deletions
diff --git a/.gitignore b/.gitignore
index e9dfb32..a7d0aa5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@
/coreupdown
/coreup
/coredown
+/coreupdownd
diff --git a/Makefile b/Makefile
index f0d383d..f9850e0 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/TODO b/TODO
deleted file mode 100644
index a399993..0000000
--- a/TODO
+++ /dev/null
@@ -1,2 +0,0 @@
-Add coreupdownd to automatically run coreup and coredown when the
-machine has high or low CPU usage
diff --git a/config.mk b/config.mk
index f1d6591..f833645 100644
--- a/config.mk
+++ b/config.mk
@@ -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
diff --git a/coredown.1 b/coredown.1
index 666ea46..e07abe2 100644
--- a/coredown.1
+++ b/coredown.1
@@ -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)
diff --git a/coreup.1 b/coreup.1
index 1dfe99f..b7fd8f2 100644
--- a/coreup.1
+++ b/coreup.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)