mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
This commit is based on the s390-tools-1.39.0 version. Changes on top of s390-tools-1.39.0: - Add MIT license to all source files - Add LICENSE file - Transform REAMDE to README.md (markdown) - Add AUTHORS.md file - Add CONTRIBUTING.md file - Move changelog from README to CHANGELOG.md file Reviewed-by: Stefan Haberland <sth@linux.vnet.ibm.com> Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
486 lines
15 KiB
C
486 lines
15 KiB
C
/*
|
|
* cpuplugd - Linux for System z Hotplug Daemon
|
|
*
|
|
* Main functions
|
|
*
|
|
* Copyright IBM Corp. 2007, 2017
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include <fcntl.h>
|
|
#include <fenv.h>
|
|
#include <sys/file.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#include "cpuplugd.h"
|
|
|
|
struct symbol_names sym_names[] = {
|
|
{ "loadavg", OP_SYMBOL_LOADAVG },
|
|
{ "runnable_proc", OP_SYMBOL_RUNABLE },
|
|
{ "onumcpus", OP_SYMBOL_CPUS },
|
|
{ "user", OP_SYMBOL_USER },
|
|
{ "nice", OP_SYMBOL_NICE },
|
|
{ "system", OP_SYMBOL_SYSTEM },
|
|
{ "idle", OP_SYMBOL_IDLE },
|
|
{ "iowait", OP_SYMBOL_IOWAIT },
|
|
{ "irq", OP_SYMBOL_IRQ },
|
|
{ "softirq", OP_SYMBOL_SOFTIRQ },
|
|
{ "steal", OP_SYMBOL_STEAL },
|
|
{ "guest_nice", OP_SYMBOL_GUEST_NICE },
|
|
{ "guest", OP_SYMBOL_GUEST },
|
|
{ "swaprate", OP_SYMBOL_SWAPRATE },
|
|
{ "apcr", OP_SYMBOL_APCR },
|
|
{ "freemem", OP_SYMBOL_FREEMEM },
|
|
{ "meminfo.", OP_SYMBOL_MEMINFO },
|
|
{ "vmstat.", OP_SYMBOL_VMSTAT },
|
|
{ "cpustat.", OP_SYMBOL_CPUSTAT },
|
|
{ "time", OP_SYMBOL_TIME },
|
|
};
|
|
|
|
struct config cfg = {
|
|
.cpu_max = -1,
|
|
.cpu_min = -1,
|
|
.update = -1,
|
|
.cmm_min = -1,
|
|
.cmm_max = -1,
|
|
.cmm_inc = NULL,
|
|
.cmm_dec = NULL,
|
|
.memplug = NULL,
|
|
.memunplug = NULL,
|
|
.hotplug = NULL,
|
|
.hotunplug = NULL,
|
|
};
|
|
|
|
int num_cpu_start, memory, cpu, reload_pending;
|
|
long cmm_pagesize_start;
|
|
unsigned long meminfo_size, vmstat_size, cpustat_size, varinfo_size;
|
|
char *meminfo, *vmstat, *cpustat, *varinfo;
|
|
double *timestamps;
|
|
unsigned int history_max, history_current, history_prev, sym_names_count;
|
|
|
|
static struct symbols symbols;
|
|
static jmp_buf jmpenv;
|
|
static struct sigaction act;
|
|
|
|
/*
|
|
* Handle the sigfpe signal which we might catch during rule evaluating
|
|
*/
|
|
static void sigfpe_handler(int UNUSED(sig))
|
|
{
|
|
longjmp(jmpenv, 1);
|
|
}
|
|
|
|
static void eval_cpu_rules(void)
|
|
{
|
|
double diffs[CPUSTATS], diffs_total, percent_factor;
|
|
char *procinfo_current, *procinfo_prev;
|
|
int cpu, nr_cpus, on_off;
|
|
|
|
nr_cpus = get_numcpus();
|
|
procinfo_current = cpustat + history_current * cpustat_size;
|
|
procinfo_prev = cpustat + history_prev * cpustat_size;
|
|
|
|
diffs[0] = get_proc_value(procinfo_current, "user", ' ') -
|
|
get_proc_value(procinfo_prev, "user", ' ');
|
|
diffs[1] = get_proc_value(procinfo_current, "nice", ' ') -
|
|
get_proc_value(procinfo_prev, "nice", ' ');
|
|
diffs[2] = get_proc_value(procinfo_current, "system", ' ') -
|
|
get_proc_value(procinfo_prev, "system", ' ');
|
|
diffs[3] = get_proc_value(procinfo_current, "idle", ' ') -
|
|
get_proc_value(procinfo_prev, "idle", ' ');
|
|
diffs[4] = get_proc_value(procinfo_current, "iowait", ' ') -
|
|
get_proc_value(procinfo_prev, "iowait", ' ');
|
|
diffs[5] = get_proc_value(procinfo_current, "irq", ' ') -
|
|
get_proc_value(procinfo_prev, "irq", ' ');
|
|
diffs[6] = get_proc_value(procinfo_current, "softirq", ' ') -
|
|
get_proc_value(procinfo_prev, "softirq", ' ');
|
|
diffs[7] = get_proc_value(procinfo_current, "steal", ' ') -
|
|
get_proc_value(procinfo_prev, "steal", ' ');
|
|
diffs[8] = get_proc_value(procinfo_current, "guest", ' ') -
|
|
get_proc_value(procinfo_prev, "guest", ' ');
|
|
diffs[9] = get_proc_value(procinfo_current, "guest_nice", ' ') -
|
|
get_proc_value(procinfo_prev, "guest_nice", ' ');
|
|
|
|
diffs_total = get_proc_value(procinfo_current, "total_ticks", ' ') -
|
|
get_proc_value(procinfo_prev, "total_ticks", ' ');
|
|
if (diffs_total == 0)
|
|
diffs_total = 1;
|
|
|
|
symbols.loadavg = get_proc_value(procinfo_current, "loadavg", ' ');
|
|
symbols.runnable_proc = get_proc_value(procinfo_current,
|
|
"runnable_proc", ' ');
|
|
symbols.onumcpus = get_proc_value(procinfo_current, "onumcpus", ' ');
|
|
|
|
percent_factor = 100 * symbols.onumcpus;
|
|
symbols.user = (diffs[0] / diffs_total) * percent_factor;
|
|
symbols.nice = (diffs[1] / diffs_total) * percent_factor;
|
|
symbols.system = (diffs[2] / diffs_total) * percent_factor;
|
|
symbols.idle = (diffs[3] / diffs_total) * percent_factor;
|
|
symbols.iowait = (diffs[4] / diffs_total) * percent_factor;
|
|
symbols.irq = (diffs[5] / diffs_total) * percent_factor;
|
|
symbols.softirq = (diffs[6] / diffs_total) * percent_factor;
|
|
symbols.steal = (diffs[7] / diffs_total) * percent_factor;
|
|
symbols.guest = (diffs[8] / diffs_total) * percent_factor;
|
|
symbols.guest_nice = (diffs[9] / diffs_total) * percent_factor;
|
|
|
|
/* only use this for development and testing */
|
|
cpuplugd_debug("cpustat values:\n%s", cpustat + history_current *
|
|
cpustat_size);
|
|
if (debug && foreground == 1) {
|
|
printf("-------------------- CPU --------------------\n");
|
|
printf("cpu_min: %ld\n", cfg.cpu_min);
|
|
printf("cpu_max: %ld\n", cfg.cpu_max);
|
|
printf("loadavg: %f \n", symbols.loadavg);
|
|
printf("user percent = %f\n", symbols.user);
|
|
printf("nice percent = %f\n", symbols.nice);
|
|
printf("system percent = %f\n", symbols.system);
|
|
printf("idle percent = %f\n", symbols.idle);
|
|
printf("iowait percent = %f\n", symbols.iowait);
|
|
printf("irq percent = %f\n", symbols.irq);
|
|
printf("softirq percent = %f\n", symbols.softirq);
|
|
printf("steal percent = %f\n", symbols.steal);
|
|
printf("guest percent = %f\n", symbols.guest);
|
|
printf("guest_nice percent = %f\n", symbols.guest_nice);
|
|
printf("numcpus %d\n", nr_cpus);
|
|
printf("runnable_proc: %d\n", (int) symbols.runnable_proc);
|
|
printf("---------------------------------------------\n");
|
|
printf("onumcpus: %d\n", (int) symbols.onumcpus);
|
|
printf("---------------------------------------------\n");
|
|
printf("hotplug: ");
|
|
print_term(cfg.hotplug);
|
|
printf("\n");
|
|
printf("hotunplug: ");
|
|
print_term(cfg.hotunplug);
|
|
printf("\n");
|
|
printf("---------------------------------------------\n");
|
|
}
|
|
|
|
on_off = 0;
|
|
/* Evaluate the hotplug rule */
|
|
if (eval_term(cfg.hotplug, &symbols))
|
|
on_off++;
|
|
/* Evaluate the hotunplug rule only if hotplug did not match */
|
|
else if (eval_term(cfg.hotunplug, &symbols))
|
|
on_off--;
|
|
if (on_off > 0) {
|
|
/* check the cpu nr limit */
|
|
if (symbols.onumcpus + 1 > cfg.cpu_max) {
|
|
/* cpu limit reached */
|
|
cpuplugd_debug("maximum cpu limit is reached\n");
|
|
return;
|
|
}
|
|
/* try to find a offline cpu */
|
|
for (cpu = 0; cpu < nr_cpus; cpu++)
|
|
if (is_online(cpu) == 0 && cpu_is_configured(cpu) != 0)
|
|
break;
|
|
if (cpu < nr_cpus) {
|
|
cpuplugd_debug("cpu with id %d is currently offline "
|
|
"and will be enabled\n", cpu);
|
|
if (hotplug(cpu) == -1)
|
|
cpuplugd_debug("unable to find a cpu which "
|
|
"can be enabled\n");
|
|
} else {
|
|
/*
|
|
* In case we tried to enable a cpu but this failed.
|
|
* This is the case if a cpu is deconfigured
|
|
*/
|
|
cpuplugd_debug("unable to find a cpu which can "
|
|
"be enabled\n");
|
|
}
|
|
} else if (on_off < 0) {
|
|
/* check cpu nr limit */
|
|
if (symbols.onumcpus <= cfg.cpu_min) {
|
|
cpuplugd_debug("minimum cpu limit is reached\n");
|
|
return;
|
|
}
|
|
/* try to find a online cpu */
|
|
for (cpu = get_numcpus() - 1; cpu >= 0; cpu--) {
|
|
if (is_online(cpu) != 0)
|
|
break;
|
|
}
|
|
if (cpu > 0) {
|
|
cpuplugd_debug("cpu with id %d is currently online "
|
|
"and will be disabled\n", cpu);
|
|
hotunplug(cpu);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void eval_mem_rules(double interval)
|
|
{
|
|
long cmmpages_size, cmm_inc, cmm_dec, cmm_new;
|
|
double free_memory, swaprate, apcr;
|
|
char *procinfo_current, *procinfo_prev;
|
|
|
|
procinfo_current = meminfo + history_current * meminfo_size;
|
|
free_memory = get_proc_value(procinfo_current, "MemFree", ':');
|
|
|
|
procinfo_current = vmstat + history_current * vmstat_size;
|
|
procinfo_prev = vmstat + history_prev * vmstat_size;
|
|
swaprate = (get_proc_value(procinfo_current, "pswpin", ' ') +
|
|
get_proc_value(procinfo_current, "pswpout", ' ') -
|
|
get_proc_value(procinfo_prev, "pswpin", ' ') -
|
|
get_proc_value(procinfo_prev, "pswpout", ' ')) /
|
|
interval;
|
|
apcr = (get_proc_value(procinfo_current, "pgpgin", ' ') +
|
|
get_proc_value(procinfo_current, "pgpgout", ' ') -
|
|
get_proc_value(procinfo_prev, "pgpgin", ' ') -
|
|
get_proc_value(procinfo_prev, "pgpgout", ' ')) /
|
|
interval;
|
|
|
|
cmmpages_size = get_cmmpages_size();
|
|
symbols.apcr = apcr; // apcr in 512 byte blocks / sec
|
|
symbols.swaprate = swaprate; // swaprate in 4K pages / sec
|
|
symbols.freemem = free_memory / 1024; // freemem in MB
|
|
|
|
cmm_inc = eval_double(cfg.cmm_inc, &symbols);
|
|
/* cmm_dec is optional */
|
|
if (cfg.cmm_dec)
|
|
cmm_dec = eval_double(cfg.cmm_dec, &symbols);
|
|
else
|
|
cmm_dec = cmm_inc;
|
|
|
|
/* only use this for development and testing */
|
|
if (debug && foreground == 1) {
|
|
printf("------------------- Memory ------------------\n");
|
|
printf("cmm_min: %ld\n", cfg.cmm_min);
|
|
printf("cmm_max: %ld\n", cfg.cmm_max);
|
|
printf("swaprate: %f\n", symbols.swaprate);
|
|
printf("apcr: %f\n", symbols.apcr);
|
|
printf("cmm_inc: %ld = ", cmm_inc);
|
|
print_term(cfg.cmm_inc);
|
|
printf("\n");
|
|
printf("cmm_dec: %ld = ", cmm_dec);
|
|
if (cfg.cmm_dec)
|
|
print_term(cfg.cmm_dec);
|
|
else
|
|
print_term(cfg.cmm_inc);
|
|
printf("\n");
|
|
printf("free memory: %f MB\n", symbols.freemem);
|
|
printf("---------------------------------------------\n");
|
|
printf("cmm_pages: %ld\n", cmmpages_size);
|
|
printf("---------------------------------------------\n");
|
|
printf("memplug: ");
|
|
print_term(cfg.memplug);
|
|
printf("\n");
|
|
printf("memunplug: ");
|
|
print_term(cfg.memunplug);
|
|
printf("\n");
|
|
printf("---------------------------------------------\n");
|
|
}
|
|
|
|
cmm_new = cmmpages_size;
|
|
/* Evaluate the memplug rule */
|
|
if (eval_term(cfg.memplug, &symbols)) {
|
|
if (cmm_dec < 0) {
|
|
cpuplugd_error("cmm_dec went negative (%ld), set it "
|
|
"to 0.\n", cmm_dec);
|
|
cmm_dec = 0;
|
|
}
|
|
cmm_new -= cmm_dec;
|
|
/* Evaluate the memunplug rule only if memplug did not match */
|
|
} else if (eval_term(cfg.memunplug, &symbols)) {
|
|
if (cmm_inc < 0) {
|
|
cpuplugd_error("cmm_inc went negative (%ld), set it "
|
|
"to 0.\n", cmm_inc);
|
|
cmm_inc = 0;
|
|
}
|
|
cmm_new += cmm_inc;
|
|
}
|
|
if (cmm_new < cfg.cmm_min) {
|
|
cpuplugd_debug("minimum memory limit is reached\n");
|
|
cmm_new = cfg.cmm_min;
|
|
}
|
|
if (cmm_new > cfg.cmm_max) {
|
|
cpuplugd_debug("maximum memory limit is reached\n");
|
|
cmm_new = cfg.cmm_max;
|
|
}
|
|
if (cmm_new != cmmpages_size)
|
|
set_cmm_pages(cmm_new);
|
|
}
|
|
|
|
static void time_read(double *timestamps)
|
|
{
|
|
struct timeval tv;
|
|
int rc;
|
|
|
|
cpuplugd_debug("\n==================== New interval "
|
|
"====================\n");
|
|
rc = gettimeofday(&tv, NULL);
|
|
if (!rc) {
|
|
*timestamps = tv.tv_sec + (double) tv.tv_usec / 1000000;
|
|
cpuplugd_debug("Timestamp: %s (%f seconds since "
|
|
"the Epoch)\n", ctime(&tv.tv_sec), *timestamps);
|
|
} else
|
|
cpuplugd_exit("gettimeofday failed: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
void setup_history()
|
|
{
|
|
/*
|
|
* The /proc file size will vary during intervals, use double of current
|
|
* size to have enough buffer for growing values.
|
|
*/
|
|
meminfo_size = proc_read_size("/proc/meminfo") * 2;
|
|
vmstat_size = proc_read_size("/proc/vmstat") * 2;
|
|
cpustat_size = CPUSTAT_SIZE;
|
|
|
|
meminfo = malloc(meminfo_size * (history_max + 1));
|
|
if (!meminfo)
|
|
cpuplugd_exit("Out of memory: meminfo\n");
|
|
vmstat = malloc(vmstat_size * (history_max + 1));
|
|
if (!vmstat)
|
|
cpuplugd_exit("Out of memory: vmstat\n");
|
|
cpustat = malloc(cpustat_size * (history_max + 1));
|
|
if (!cpustat)
|
|
cpuplugd_exit("Out of memory: cpustat\n");
|
|
timestamps = malloc(sizeof(double) * (history_max + 1));
|
|
if (!timestamps)
|
|
cpuplugd_exit("Out of memory: timestamps\n");
|
|
|
|
/*
|
|
* Read history data, at least 1 interval for swaprate, apcr, idle, etc.
|
|
*/
|
|
history_current = 0;
|
|
cpuplugd_info("Waiting %i intervals to accumulate history.\n",
|
|
history_max);
|
|
do {
|
|
time_read(×tamps[history_current]);
|
|
proc_read(meminfo + history_current * meminfo_size,
|
|
"/proc/meminfo", meminfo_size);
|
|
proc_read(vmstat + history_current * vmstat_size,
|
|
"/proc/vmstat", vmstat_size);
|
|
proc_cpu_read(cpustat + history_current * cpustat_size);
|
|
sleep(cfg.update);
|
|
history_current++;
|
|
} while (history_current < history_max);
|
|
history_current--;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
double interval;
|
|
int fd, rc;
|
|
|
|
reload_pending = 0;
|
|
sym_names_count = sizeof(sym_names) / sizeof(struct symbol_names);
|
|
varinfo_size = VARINFO_SIZE;
|
|
varinfo = calloc(varinfo_size, 1);
|
|
if (!varinfo) {
|
|
cpuplugd_error("Out of memory: varinfo\n");
|
|
exit(1);
|
|
}
|
|
/*
|
|
* varinfo must start with '\n' for correct string matching
|
|
* in get_var_rvalue().
|
|
*/
|
|
varinfo[0] = '\n';
|
|
|
|
/* Parse the command line options */
|
|
parse_options(argc, argv);
|
|
|
|
/* flock() lock file to prevent multiple instances of cpuplugd */
|
|
fd = open(LOCKFILE, O_CREAT | O_RDONLY, S_IRUSR);
|
|
if (fd == -1) {
|
|
cpuplugd_error("Cannot open lock file %s: %s\n", LOCKFILE,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
rc = flock(fd, LOCK_EX | LOCK_NB);
|
|
if (rc) {
|
|
cpuplugd_error("flock() failed on lock file %s: %s\nThis might "
|
|
"indicate that an instance of this daemon is "
|
|
"already running.\n", LOCKFILE, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
/* Make sure that the daemon is not started multiple times */
|
|
check_if_started_twice();
|
|
/* Store daemon pid also in foreground mode */
|
|
handle_signals();
|
|
handle_sighup();
|
|
|
|
/* Need 1 history level minimum for internal symbols */
|
|
history_max = 1;
|
|
/*
|
|
* Parse arguments from the configuration file, also calculate
|
|
* history_max
|
|
*/
|
|
parse_configfile(configfile);
|
|
if (history_max > MAX_HISTORY)
|
|
cpuplugd_exit("History depth %i exceeded maximum (%i)\n",
|
|
history_max, MAX_HISTORY);
|
|
/* Check the settings in the configuration file */
|
|
check_config();
|
|
|
|
if (!foreground) {
|
|
rc = daemon(1, 0);
|
|
if (rc < 0)
|
|
cpuplugd_exit("Detach from terminal failed: %s\n",
|
|
strerror(errno));
|
|
}
|
|
/* Store daemon pid */
|
|
store_pid();
|
|
/* Unlock lock file */
|
|
flock(fd, LOCK_UN);
|
|
close(fd);
|
|
|
|
/* Install signal handler for floating point exceptions */
|
|
rc = feenableexcept(FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW |
|
|
FE_INVALID);
|
|
act.sa_flags = SA_NODEFER;
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_handler = sigfpe_handler;
|
|
if (sigaction(SIGFPE, &act, NULL) < 0)
|
|
cpuplugd_exit("sigaction( SIGFPE, ... ) failed - reason %s\n",
|
|
strerror(errno));
|
|
|
|
setup_history();
|
|
|
|
/* Main loop */
|
|
while (1) {
|
|
if (reload_pending) { // check for daemon reload
|
|
reload_daemon();
|
|
reload_pending = 0;
|
|
}
|
|
|
|
history_prev = history_current;
|
|
history_current = (history_current + 1) % (history_max + 1);
|
|
time_read(×tamps[history_current]);
|
|
proc_read(meminfo + history_current * meminfo_size,
|
|
"/proc/meminfo", meminfo_size);
|
|
proc_read(vmstat + history_current * vmstat_size,
|
|
"/proc/vmstat", vmstat_size);
|
|
proc_cpu_read(cpustat + history_current * cpustat_size);
|
|
interval = timestamps[history_current] -
|
|
timestamps[history_prev];
|
|
cpuplugd_debug("config update interval: %ld seconds\n",
|
|
cfg.update);
|
|
cpuplugd_debug("real update interval: %f seconds\n", interval);
|
|
|
|
/* Run code that may signal failure via longjmp. */
|
|
if (cpu == 1) {
|
|
if (setjmp(jmpenv) == 0)
|
|
eval_cpu_rules();
|
|
else
|
|
cpuplugd_error("Floating point exception, "
|
|
"skipping cpu rule "
|
|
"evaluation.\n");
|
|
}
|
|
if (memory == 1) {
|
|
if (setjmp(jmpenv) == 0)
|
|
eval_mem_rules(interval);
|
|
else
|
|
cpuplugd_error("Floating point exception, "
|
|
"skipping memory rule "
|
|
"evaluation.\n");
|
|
}
|
|
sleep(cfg.update);
|
|
}
|
|
return 0;
|
|
}
|