/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include static const char *argv0; #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 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; }