aboutsummaryrefslogblamecommitdiffstats
path: root/coreupdown.c
blob: 70015ecb96cfdbf3f5561a03934d3400076476b1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10




                                                         
                   
                   


                   
                 

                   
                         
                                               





                                      
 

                                                                                    

                        













                                                                               
         

                   
 


                                                            










                                                              
                   










                                                 














































                                                                                                
                                      
















                                                                   
                 



















                                                                                                     
                 

























































                                                                                                     
                          
                          

         










                                            






                          
          
                                                                                 


                              
                                                          

                                      



                                        









                                                      


                                                   
 
                                                        
 

                                                                                              










                                                











                                                                                                    
                            
                                                   



                                                                                                   
                                                                                         
                 










































                                                                                                      


                                                                               






                                                   
 
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

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 <function>: %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;
}