/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #include static const char *argv0; static volatile sig_atomic_t caught_sighup = 0; #define coreup() coreupdown(ULONG_MAX) #define coredown() coreupdown(1) static int coreupdown(unsigned long int count) { char path[sizeof("/sys/devices/system/cpu/cpu/online") + 3 * sizeof(count)]; unsigned long int i; int fd, ret = 0; 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; } static int oneshot_main(int argc, char **argv, unsigned long int count) { if (argc && !strcmp(argv[0], "--")) { argv++; argc--; } if (argc > 1) { usage: fprintf(stderr, "usage: %s [count]\n", argv0); return 1; } if (argc) { char *end; errno = 0; if (!isdigit(**argv)) goto usage; count = strtoul(*argv, &end, 10); if (!count || errno || *end) goto usage; argv++; argc--; } 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; } 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; } } } 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; } 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 void sighup_handler(int signo) { (void) signo; caught_sighup = 1; } static int daemon_main(int argc, char **argv, int orig_argc, char **orig_argv, int reexeced) { uintmax_t total, idle; size_t cpus; int up_time = 0, down_time = 0, cooldown = 0, err; struct timespec interval, rem; restart: if (reexeced) argv[0] = "coreupdownd"; if (argc && !strcmp(argv[0], "--")) { argv++; argc--; } if (argc > 0) { fprintf(stderr, "usage: %s\n", argv0); return 1; } if (!reexeced) { /* TODO add proper daemonisation */ } /* TODO configurations should be configurable */ if (signal(SIGHUP, sighup_handler) == SIG_ERR) fprintf(stderr, "%s: signal SIGHUP : %s\n", argv0, strerror(errno)); 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 (caught_sighup) { caught_sighup = 0; orig_argv[0] = "coreupdownd "; execv(DAEMON_PATH, orig_argv); fprintf(stderr, "%s: execv %s: %s\n", argv0, DAEMON_PATH, strerror(errno)); reexeced = 1; argv = &orig_argv[1]; argc = orig_argc - 1; goto restart; } if ((err = clock_nanosleep(CLOCK_REALTIME /* = monotonic */, 0, &interval, &rem))) { do { if (err != EINTR) { fprintf(stderr, "%s: clock_nanosleep CLOCK_REALTIME: %s\n", argv0, strerror(errno)); return 1; } } while ((err = clock_nanosleep(CLOCK_REALTIME, 0, &rem, &rem))); } 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, argc + 1, &argv[-1], 0); if (!strcmp(command, "coreupdownd ")) return daemon_main(argc, argv, argc + 1, &argv[-1], 1); } fprintf(stderr, "usage: coreup [count]\n" "usage: coredown [count]\n" "usage: coreupdownd\n"); return 1; }