mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Initial s390-tools-2.0.0 import
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>
This commit is contained in:
23
cpuplugd/Makefile
Normal file
23
cpuplugd/Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
include ../common.mak
|
||||
|
||||
all: cpuplugd
|
||||
|
||||
LDLIBS += -lm
|
||||
|
||||
OBJECTS = daemon.o cpu.o info.o terms.o config.o main.o getopt.o mem.o
|
||||
|
||||
cpuplugd: $(OBJECTS)
|
||||
$(LINK) $(ALL_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
||||
|
||||
clean:
|
||||
rm -f cpuplugd $(OBJECTS)
|
||||
|
||||
install: all
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 cpuplugd \
|
||||
$(DESTDIR)$(USRSBINDIR)
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/cpuplugd.8 \
|
||||
$(DESTDIR)$(MANDIR)/man8
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 man/cpuplugd.conf.5 \
|
||||
$(DESTDIR)$(MANDIR)/man5
|
||||
|
||||
.PHONY: all install clean
|
||||
336
cpuplugd/config.c
Normal file
336
cpuplugd/config.c
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* Config file parsing
|
||||
*
|
||||
* 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 "cpuplugd.h"
|
||||
|
||||
/*
|
||||
* Return the value of a variable which parse_config() found within the
|
||||
* configuration file. Use only for values valid >= 0, because -1 is returned
|
||||
* in error case.
|
||||
*/
|
||||
static long parse_positive_value(char *ptr)
|
||||
{
|
||||
long value = 0;
|
||||
unsigned int i;
|
||||
|
||||
if (ptr == NULL)
|
||||
return -1;
|
||||
for (i = 0; i < strlen(ptr); i++) {
|
||||
if (isdigit(ptr[i]) == 0)
|
||||
return -1;
|
||||
}
|
||||
sscanf(ptr, "%ld", &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
char *get_var_rvalue(char *var_name)
|
||||
{
|
||||
char tmp_name[MAX_VARNAME + 3]; /* +3 for '\0', '=' and '\n' */
|
||||
unsigned int tmp_length;
|
||||
char *rvalue;
|
||||
|
||||
tmp_name[0] = '\n';
|
||||
strncpy(&tmp_name[1], var_name, MAX_VARNAME + 1); /* +1 for '\0' */
|
||||
tmp_length = strlen(tmp_name);
|
||||
tmp_name[tmp_length] = '=';
|
||||
tmp_name[tmp_length + 1] = '\0';
|
||||
rvalue = strstr(varinfo, tmp_name);
|
||||
if (rvalue == NULL)
|
||||
return NULL;
|
||||
rvalue += strlen(tmp_name);
|
||||
return rvalue;
|
||||
}
|
||||
|
||||
static void add_var(char *name, char *rvalue)
|
||||
{
|
||||
size_t offset, size;
|
||||
unsigned int i;
|
||||
|
||||
if (get_var_rvalue(name))
|
||||
cpuplugd_exit("Variable defined twice: %s\n", name);
|
||||
for (i = 0; i < sym_names_count; i++) {
|
||||
if (strncmp(name, sym_names[i].name,
|
||||
MAX(strlen(sym_names[i].name), strlen(name))) != 0)
|
||||
continue;
|
||||
cpuplugd_exit("Cannot use (pre-defined) variable name: %s\n",
|
||||
name);
|
||||
}
|
||||
|
||||
offset = strlen(varinfo);
|
||||
/* +3 because of extra '=', '\n' and '\0' */
|
||||
size = offset + strlen(name) + strlen(rvalue) + 3;
|
||||
if (size > varinfo_size)
|
||||
//TODO realloc?
|
||||
cpuplugd_exit("buffer for variables too small: need %ld, "
|
||||
"have %ld (bytes)\n", size, varinfo_size);
|
||||
size -= offset;
|
||||
snprintf(&varinfo[offset], size, "%s=%s\n", name, rvalue);
|
||||
return;
|
||||
}
|
||||
|
||||
static int check_term(char *symbol, char *name, char *rvalue, struct term **term)
|
||||
{
|
||||
if (!strncasecmp(name, symbol, strlen(symbol))) {
|
||||
cpuplugd_debug("found the following rule: %s = %s\n",
|
||||
name, rvalue);
|
||||
*term = parse_term(&rvalue, OP_PRIO_NONE);
|
||||
if (rvalue[0] == '\0')
|
||||
return 1;
|
||||
cpuplugd_exit("parsing error at %s, position: %s\n", symbol,
|
||||
rvalue);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_value(char *symbol, char *name, char *rvalue, long *value)
|
||||
{
|
||||
if (!strncasecmp(name, symbol, strlen(symbol))) {
|
||||
*value = parse_positive_value(rvalue);
|
||||
cpuplugd_debug("found %s value: %ld\n", symbol, *value);
|
||||
if (*value >= 0)
|
||||
return 1;
|
||||
cpuplugd_exit("parsing error at update\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a single line of the configuration file
|
||||
*/
|
||||
static void parse_configline(char *line)
|
||||
{
|
||||
char *match, *name, *rvalue, *start, *stop;
|
||||
int i, j;
|
||||
size_t len;
|
||||
char temp[strlen(line) + 1];
|
||||
|
||||
if (line[0] == '#')
|
||||
return;
|
||||
for (i = j = 0; line[i] != 0; i++) /* Remove whitespace. */
|
||||
if (!isblank(line[i]) && !isspace(line[i]))
|
||||
temp[j++] = line[i];
|
||||
temp[j] = '\0';
|
||||
match = strchr(temp, '=');
|
||||
if (match == NULL)
|
||||
return;
|
||||
*match = '\0'; /* Separate name and right hand value */
|
||||
name = temp; /* left side of = */
|
||||
rvalue = match + 1; /* right side of = */
|
||||
/*
|
||||
* remove the double quotes
|
||||
* example: CPU_MIN="2"
|
||||
*/
|
||||
start = strchr(rvalue, '"'); /* points to first " */
|
||||
stop = strrchr(rvalue, '"'); /* points to last " */
|
||||
len = stop - start;
|
||||
if (start != NULL && stop != NULL && len > 0) {
|
||||
rvalue[len] = '\0';
|
||||
rvalue = rvalue + 1;
|
||||
} else
|
||||
cpuplugd_exit("the configuration file has syntax "
|
||||
"errors at %s, position: %s\n", name, rvalue);
|
||||
|
||||
if (check_term("hotplug", name, rvalue, &cfg.hotplug))
|
||||
return;
|
||||
if (check_term("hotunplug", name, rvalue, &cfg.hotunplug))
|
||||
return;
|
||||
if (check_term("memplug", name, rvalue, &cfg.memplug))
|
||||
return;
|
||||
if (check_term("memunplug", name, rvalue, &cfg.memunplug))
|
||||
return;
|
||||
if (check_term("cmm_inc", name, rvalue, &cfg.cmm_inc))
|
||||
return;
|
||||
if (check_term("cmm_dec", name, rvalue, &cfg.cmm_dec))
|
||||
return;
|
||||
|
||||
if (check_value("update", name, rvalue, &cfg.update)) {
|
||||
if (cfg.update > 0)
|
||||
return;
|
||||
cpuplugd_exit("update must be > 0\n");
|
||||
}
|
||||
if (check_value("cpu_min", name, rvalue, &cfg.cpu_min)) {
|
||||
if (cfg.cpu_min > 0)
|
||||
return;
|
||||
cpuplugd_exit("cpu_min must be > 0\n");
|
||||
}
|
||||
if (check_value("cpu_max", name, rvalue, &cfg.cpu_max)) {
|
||||
if (cfg.cpu_max == 0)
|
||||
/* if cpu_max is 0, we use the overall number of cpus */
|
||||
cfg.cpu_max = get_numcpus();
|
||||
return;
|
||||
}
|
||||
if (check_value("cmm_min", name, rvalue, &cfg.cmm_min))
|
||||
return;
|
||||
if (check_value("cmm_max", name, rvalue, &cfg.cmm_max))
|
||||
return;
|
||||
|
||||
cpuplugd_debug("found the following variable: %s = %s\n",
|
||||
name, rvalue);
|
||||
if (strlen(name) > MAX_VARNAME)
|
||||
cpuplugd_exit("Variable name too long (max. length is "
|
||||
"%i chars): %s\n", MAX_VARNAME, name);
|
||||
add_var(name, rvalue);
|
||||
}
|
||||
|
||||
/*
|
||||
* Function used to parse the min and max values at the beginning of the
|
||||
* configuration file as well as the hotplug and hotunplug rules.
|
||||
*/
|
||||
void parse_configfile(char *file)
|
||||
{
|
||||
char linebuffer[MAX_LINESIZE + 2]; /* current line incl. \n and \0 */
|
||||
char *linep_offset;
|
||||
FILE *filp;
|
||||
|
||||
filp = fopen(file, "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("Opening configuration file failed: %s\n",
|
||||
strerror(errno));
|
||||
while (fgets(linebuffer, sizeof(linebuffer), filp) != NULL) {
|
||||
if (!(linep_offset = strchr(linebuffer, '\n')))
|
||||
cpuplugd_exit("Line is too long (max. length is %i "
|
||||
"characters): %s\n", MAX_LINESIZE,
|
||||
linebuffer);
|
||||
parse_configline(linebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the required settings are found in the configuration file.
|
||||
* "Autodetect" if cpu and/or memory hotplug configuration entries
|
||||
* where specified
|
||||
*/
|
||||
void check_config()
|
||||
{
|
||||
int cpuid;
|
||||
int lpar_status;
|
||||
|
||||
lpar_status = check_lpar();
|
||||
if (cfg.update < 0)
|
||||
cpuplugd_exit("No valid update interval specified.\n");
|
||||
if (cfg.cpu_max < cfg.cpu_min && cfg.cpu_max != 0)
|
||||
cpuplugd_exit("cpu_max below cpu_min, aborting.\n");
|
||||
if (cfg.cpu_max < 0 || cfg.cpu_min < 0 || cfg.hotplug == NULL ||
|
||||
cfg.hotunplug == NULL) {
|
||||
cpuplugd_error("No valid CPU hotplug configuration "
|
||||
"detected.\n");
|
||||
cpu = 0;
|
||||
} else {
|
||||
cpu = 1;
|
||||
cpuplugd_debug("Valid CPU hotplug configuration detected.\n");
|
||||
}
|
||||
if (cfg.cmm_max < 0 || cfg.cmm_min < 0 || cfg.memplug == NULL ||
|
||||
cfg.memunplug == NULL || cfg.cmm_inc == NULL ||
|
||||
cfg.cmm_max < cfg.cmm_min) {
|
||||
cpuplugd_error("No valid memory hotplug configuration "
|
||||
"detected.\n");
|
||||
memory = 0;
|
||||
} else {
|
||||
memory = 1;
|
||||
/*
|
||||
* check if all the necessary files exit below /proc
|
||||
*/
|
||||
if (check_cmmfiles() != 0 && lpar_status == 0) {
|
||||
cpuplugd_info("Can not open /proc/sys/vm/cmm_pages. "
|
||||
"The memory hotplug function will be "
|
||||
"disabled.\n");
|
||||
memory = 0;
|
||||
}
|
||||
if (memory == 1 && lpar_status == 0)
|
||||
cpuplugd_debug("Valid memory hotplug configuration "
|
||||
"detected.\n");
|
||||
if (memory == 1 && lpar_status == 1) {
|
||||
cpuplugd_debug("Valid memory hotplug configuration "
|
||||
"detected inside LPAR. "
|
||||
"The memory hotplug function will be "
|
||||
"disabled. \n");
|
||||
memory = 0;
|
||||
}
|
||||
}
|
||||
if (memory == 0 && cpu == 0)
|
||||
cpuplugd_exit("Exiting, because neither a valid cpu nor a val"
|
||||
"id memory hotplug configuration was found.\n");
|
||||
/*
|
||||
* Save the number of online cpus and the cmm_pagesize at startup,
|
||||
* so that we can enable exactly the same amount when the daemon ends
|
||||
*/
|
||||
if (cpu) {
|
||||
num_cpu_start = get_num_online_cpus();
|
||||
cpuplugd_debug("Daemon started with %d active cpus.\n",
|
||||
num_cpu_start);
|
||||
/*
|
||||
* Check that the initial number of cpus is not below the
|
||||
* minimum
|
||||
*/
|
||||
if (num_cpu_start < cfg.cpu_min &&
|
||||
get_numcpus() >= cfg.cpu_min) {
|
||||
cpuplugd_debug("The number of online cpus is below "
|
||||
"the minimum and will be increased.\n");
|
||||
cpuid = 0;
|
||||
while (get_num_online_cpus() < cfg.cpu_min &&
|
||||
cpuid < get_numcpus()) {
|
||||
if (is_online(cpuid) == 1) {
|
||||
cpuid++;
|
||||
continue;
|
||||
}
|
||||
cpuplugd_debug("cpu with id %d is currently offline "
|
||||
"and will be enabled\n", cpuid);
|
||||
hotplug(cpuid);
|
||||
cpuid++;
|
||||
}
|
||||
}
|
||||
if (get_num_online_cpus() > cfg.cpu_max) {
|
||||
cpuplugd_debug("The number of online cpus is above the maximum"
|
||||
" and will be decreased.\n");
|
||||
cpuid = 0;
|
||||
while (get_num_online_cpus() > cfg.cpu_max &&
|
||||
cpuid < get_numcpus()) {
|
||||
if (is_online(cpuid) != 1) {
|
||||
cpuid++;
|
||||
continue;
|
||||
}
|
||||
cpuplugd_debug("cpu with id %d is currently online "
|
||||
"and will be disabled\n", cpuid);
|
||||
hotunplug(cpuid);
|
||||
cpuid++;
|
||||
}
|
||||
}
|
||||
if (cfg.cpu_min > get_numcpus())
|
||||
/*
|
||||
* This check only works if nobody used the
|
||||
* additional_cpus in the boot parameter section
|
||||
*/
|
||||
cpuplugd_exit("The minimum amount of cpus is above "
|
||||
"the number of available cpus.\n"
|
||||
"Detected %d available cpus\n",
|
||||
get_numcpus());
|
||||
if (get_num_online_cpus() < cfg.cpu_min)
|
||||
cpuplugd_exit("Failed to set the number of online "
|
||||
"cpus to the minimum. Aborting.\n");
|
||||
}
|
||||
if (memory == 1) {
|
||||
/*
|
||||
* Check that the initial value of cmm_pages is not below
|
||||
* cmm_min or above cmm_max
|
||||
*/
|
||||
cmm_pagesize_start = get_cmmpages_size();
|
||||
if (cmm_pagesize_start < cfg.cmm_min) {
|
||||
cpuplugd_debug("cmm_pages is below minimum and will "
|
||||
"be increased.\n");
|
||||
set_cmm_pages(cfg.cmm_min);
|
||||
}
|
||||
if (cmm_pagesize_start > cfg.cmm_max) {
|
||||
cpuplugd_debug("cmm_pages is above the maximum and will"
|
||||
" be decreased.\n");
|
||||
set_cmm_pages(cfg.cmm_max);
|
||||
}
|
||||
}
|
||||
}
|
||||
227
cpuplugd/cpu.c
Normal file
227
cpuplugd/cpu.c
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* CPU hotplug 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 <limits.h>
|
||||
#include "cpuplugd.h"
|
||||
|
||||
|
||||
/*
|
||||
* Return overall number of available cpus. This does not necessarily
|
||||
* mean that those are currently online
|
||||
*/
|
||||
int get_numcpus()
|
||||
{
|
||||
int i;
|
||||
char path[PATH_MAX];
|
||||
int number = 0;
|
||||
|
||||
for (i = 0; ; i++) {
|
||||
/* check whether file exists and is readable */
|
||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
|
||||
if (access(path, R_OK) == 0)
|
||||
number++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return number of online cpus
|
||||
*/
|
||||
int get_num_online_cpus()
|
||||
{
|
||||
FILE *filp;
|
||||
int i;
|
||||
char path[PATH_MAX];
|
||||
int status = 0;
|
||||
int value_of_onlinefile, rc;
|
||||
|
||||
for (i = 0; i <= get_numcpus(); i++) {
|
||||
/* check wether file exists and is readable */
|
||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
|
||||
if (access(path, R_OK) != 0)
|
||||
continue;
|
||||
filp = fopen(path, "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("Cannot open cpu online file: "
|
||||
"%s\n", strerror(errno));
|
||||
else {
|
||||
rc = fscanf(filp, "%d", &value_of_onlinefile);
|
||||
if (rc != 1)
|
||||
cpuplugd_exit("Cannot read cpu online file: "
|
||||
"%s\n", strerror(errno));
|
||||
if (value_of_onlinefile == 1)
|
||||
status++;
|
||||
}
|
||||
fclose(filp);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable a certain cpu
|
||||
*/
|
||||
int hotplug(int cpuid)
|
||||
{
|
||||
FILE *filp;
|
||||
char path[PATH_MAX];
|
||||
int status, rc;
|
||||
|
||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", cpuid);
|
||||
if (access(path, W_OK) == 0) {
|
||||
filp = fopen(path, "w");
|
||||
if (!filp)
|
||||
cpuplugd_exit("Cannot open cpu online file: %s\n",
|
||||
strerror(errno));
|
||||
fprintf(filp, "1");
|
||||
fclose(filp);
|
||||
/*
|
||||
* check if the attempt to enable the cpus really worked
|
||||
*/
|
||||
filp = fopen(path, "r");
|
||||
rc = fscanf(filp, "%d", &status);
|
||||
if (rc != 1)
|
||||
cpuplugd_exit("Cannot open cpu online file: %s\n",
|
||||
strerror(errno));
|
||||
fclose(filp);
|
||||
if (status == 1) {
|
||||
cpuplugd_debug("cpu with id %d enabled\n", cpuid);
|
||||
return 1;
|
||||
} else {
|
||||
cpuplugd_debug("failed to enable cpu with id %d\n",
|
||||
cpuid);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
cpuplugd_error("hotplugging cpu with id %d failed\n", cpuid);
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable a certain cpu
|
||||
*/
|
||||
int hotunplug(int cpuid)
|
||||
{
|
||||
FILE *filp;
|
||||
int state, rc;
|
||||
int retval = -1;
|
||||
char path[PATH_MAX];
|
||||
|
||||
state = -1;
|
||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", cpuid);
|
||||
if (access(path, W_OK) == 0) {
|
||||
filp = fopen(path, "w");
|
||||
fprintf(filp, "0");
|
||||
fclose(filp);
|
||||
/*
|
||||
* Check if the attempt to enable the cpus really worked
|
||||
*/
|
||||
filp = fopen(path, "r");
|
||||
rc = fscanf(filp, "%d", &state);
|
||||
if (rc != 1)
|
||||
cpuplugd_error("Failed to disable cpu with id %d\n",
|
||||
cpuid);
|
||||
fclose(filp);
|
||||
if (state == 0)
|
||||
return 1;
|
||||
} else {
|
||||
cpuplugd_error("unplugging cpu with id %d failed\n", cpuid);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a certain cpu is currently online
|
||||
*/
|
||||
int is_online(int cpuid)
|
||||
{
|
||||
FILE *filp;
|
||||
int state;
|
||||
int retval, rc;
|
||||
char path[PATH_MAX];
|
||||
|
||||
retval = -1;
|
||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", cpuid);
|
||||
if (access(path, R_OK) == 0) {
|
||||
filp = fopen(path, "r");
|
||||
rc = fscanf(filp, "%d", &state);
|
||||
if (rc == 1) {
|
||||
if (state == 1)
|
||||
retval = 1;
|
||||
if (state == 0)
|
||||
retval = 0;
|
||||
fclose(filp);
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup method. If the daemon is stopped, we (re)activate all cpus
|
||||
*/
|
||||
void reactivate_cpus()
|
||||
{
|
||||
/*
|
||||
* Only enable the number of cpus which where
|
||||
* available at daemon startup time
|
||||
*/
|
||||
int cpuid, nc;
|
||||
|
||||
cpuid = 0;
|
||||
/* suppress verbose messages on exit */
|
||||
debug = 0;
|
||||
/*
|
||||
* We check for num_cpu_start != 0 because we might want to
|
||||
* clean up, before we queried for the number on cpus at
|
||||
* startup
|
||||
*/
|
||||
if (num_cpu_start == 0)
|
||||
return;
|
||||
while (get_num_online_cpus() != num_cpu_start && cpuid < get_numcpus()) {
|
||||
nc = get_num_online_cpus();
|
||||
if (nc == num_cpu_start)
|
||||
return;
|
||||
if (nc > num_cpu_start && is_online(cpuid) == 1)
|
||||
hotunplug(cpuid);
|
||||
if (nc < num_cpu_start && is_online(cpuid) == 0)
|
||||
hotplug(cpuid);
|
||||
cpuid++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In kernels > 2.6.24 cpus can be deconfigured. The following functions is used
|
||||
* to check if a certain cpus is in a deconfigured state.
|
||||
*/
|
||||
int cpu_is_configured(int cpuid)
|
||||
{
|
||||
FILE *filp;
|
||||
int retval, state, rc;
|
||||
char path[4096];
|
||||
|
||||
retval = -1;
|
||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/configure", cpuid);
|
||||
if (access(path, R_OK) == 0) {
|
||||
filp = fopen(path, "r");
|
||||
rc = fscanf(filp, "%d", &state);
|
||||
if (rc == 1) {
|
||||
if (state == 1)
|
||||
retval = 1;
|
||||
if (state == 0)
|
||||
retval = 0;
|
||||
fclose(filp);
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
234
cpuplugd/cpuplugd.h
Normal file
234
cpuplugd/cpuplugd.h
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* Header file
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __USE_ISOC99
|
||||
#define __USE_ISOC99
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/zt_common.h"
|
||||
|
||||
#define NAME "cpuplugd"
|
||||
#define MAX_HISTORY 100
|
||||
#define PIDFILE "/var/run/cpuplugd.pid"
|
||||
#define LOCKFILE "/var/lock/cpuplugd.lock"
|
||||
#define PROCINFO_LINE 512
|
||||
#define CPUSTAT_SIZE 1024
|
||||
#define VARINFO_SIZE 4096
|
||||
#define MAX_VARNAME 128
|
||||
#define MAX_LINESIZE 2048
|
||||
#define CPUSTATS 10
|
||||
|
||||
/*
|
||||
* Precedence of C operators
|
||||
* full list:
|
||||
* http://www.imada.sdu.dk/~svalle/
|
||||
* courses/dm14-2005/mirror/c/_7193_tabular246.gif
|
||||
*
|
||||
* ()
|
||||
* +-
|
||||
* * /
|
||||
* < >
|
||||
* &
|
||||
* |
|
||||
*/
|
||||
enum op_prio {
|
||||
OP_PRIO_NONE,
|
||||
OP_PRIO_OR,
|
||||
OP_PRIO_AND,
|
||||
/* greater and lower */
|
||||
OP_PRIO_CMP,
|
||||
OP_PRIO_ADD,
|
||||
OP_PRIO_MULT
|
||||
};
|
||||
|
||||
enum operation {
|
||||
/* Leaf operators */
|
||||
OP_SYMBOL_LOADAVG,
|
||||
OP_SYMBOL_RUNABLE,
|
||||
OP_SYMBOL_CPUS,
|
||||
OP_SYMBOL_USER,
|
||||
OP_SYMBOL_NICE,
|
||||
OP_SYMBOL_SYSTEM,
|
||||
OP_SYMBOL_IDLE,
|
||||
OP_SYMBOL_IOWAIT,
|
||||
OP_SYMBOL_IRQ,
|
||||
OP_SYMBOL_SOFTIRQ,
|
||||
OP_SYMBOL_STEAL,
|
||||
OP_SYMBOL_GUEST,
|
||||
OP_SYMBOL_GUEST_NICE,
|
||||
OP_SYMBOL_APCR,
|
||||
OP_SYMBOL_SWAPRATE,
|
||||
OP_SYMBOL_FREEMEM,
|
||||
OP_SYMBOL_MEMINFO,
|
||||
OP_SYMBOL_VMSTAT,
|
||||
OP_SYMBOL_CPUSTAT,
|
||||
OP_SYMBOL_TIME,
|
||||
OP_CONST,
|
||||
/* Unary operators */
|
||||
OP_NEG,
|
||||
OP_NOT,
|
||||
/* Binary operators */
|
||||
OP_AND,
|
||||
OP_OR,
|
||||
OP_GREATER,
|
||||
OP_LESSER,
|
||||
OP_PLUS,
|
||||
OP_MINUS,
|
||||
OP_MULT,
|
||||
OP_DIV,
|
||||
/* ... */
|
||||
/*Variables which are eligible within rules*/
|
||||
VAR_LOAD, /* loadaverage */
|
||||
VAR_RUN, /* number of runnable processes */
|
||||
VAR_ONLINE /* number of online cpus */
|
||||
};
|
||||
|
||||
struct symbols {
|
||||
double loadavg;
|
||||
double runnable_proc;
|
||||
double onumcpus;
|
||||
double idle;
|
||||
double freemem;
|
||||
double apcr;
|
||||
double swaprate;
|
||||
double user;
|
||||
double nice;
|
||||
double system;
|
||||
double iowait;
|
||||
double irq;
|
||||
double softirq;
|
||||
double steal;
|
||||
double guest;
|
||||
double guest_nice;
|
||||
};
|
||||
|
||||
struct term {
|
||||
enum operation op;
|
||||
double value;
|
||||
struct term *left, *right;
|
||||
char *proc_name;
|
||||
unsigned int index;
|
||||
};
|
||||
|
||||
/*
|
||||
* List of argurments taken fromt the configuration file
|
||||
*
|
||||
*/
|
||||
struct config {
|
||||
long cpu_max;
|
||||
long cpu_min;
|
||||
long update;
|
||||
long cmm_max;
|
||||
long cmm_min;
|
||||
struct term *cmm_inc;
|
||||
struct term *cmm_dec;
|
||||
struct term *hotplug;
|
||||
struct term *hotunplug;
|
||||
struct term *memplug;
|
||||
struct term *memunplug;
|
||||
};
|
||||
|
||||
struct symbol_names {
|
||||
char *name;
|
||||
enum operation symop;
|
||||
};
|
||||
|
||||
extern int foreground;
|
||||
extern int debug;
|
||||
extern char *configfile;
|
||||
extern int debug; /* is verbose specified? */
|
||||
extern int memory;
|
||||
extern int cpu;
|
||||
extern int num_cpu_start; /* # of online cpus at the time of the startup */
|
||||
extern long cmm_pagesize_start; /* cmm_pageize at the time of daemon startup */
|
||||
extern struct config cfg;
|
||||
extern int reload_pending;
|
||||
extern unsigned long meminfo_size;
|
||||
extern unsigned long vmstat_size;
|
||||
extern unsigned long cpustat_size;
|
||||
extern unsigned long varinfo_size;
|
||||
extern char *meminfo;
|
||||
extern char *vmstat;
|
||||
extern char *cpustat;
|
||||
extern char *varinfo;
|
||||
extern double *timestamps;
|
||||
extern unsigned int history_max;
|
||||
extern unsigned int history_current;
|
||||
extern struct symbol_names sym_names[];
|
||||
extern unsigned int sym_names_count;
|
||||
|
||||
int get_numcpus();
|
||||
int get_num_online_cpus();
|
||||
void get_loadavg_runnable(double *loadavg, double *runnable);
|
||||
void clean_up();
|
||||
void reactivate_cpus();
|
||||
void parse_configfile(char *file);
|
||||
void print_term(struct term *fn);
|
||||
struct term *parse_term(char **p, enum op_prio prio);
|
||||
int eval_term(struct term *fn, struct symbols *symbols);
|
||||
double eval_double(struct term *fn, struct symbols *symbols);
|
||||
double get_proc_value(char *procinfo, char *name, char separator);
|
||||
void proc_read(char *procinfo, char *path, unsigned long size);
|
||||
void proc_cpu_read(char *procinfo);
|
||||
unsigned long proc_read_size(char *path);
|
||||
char *get_var_rvalue(char *var_name);
|
||||
void cleanup_cmm(void);
|
||||
int hotplug(int cpuid);
|
||||
int hotunplug(int cpuid);
|
||||
int is_online(int cpuid);
|
||||
long get_cmmpages_size();
|
||||
void parse_options(int argc, char **argv);
|
||||
void check_if_started_twice();
|
||||
void store_pid(void);
|
||||
void handle_signals(void);
|
||||
void handle_sighup(void);
|
||||
void reload_daemon(void);
|
||||
int check_cmmfiles(void);
|
||||
void check_config();
|
||||
void set_cmm_pages(long size);
|
||||
int check_lpar();
|
||||
int cpu_is_configured(int cpuid);
|
||||
void setup_history(void);
|
||||
|
||||
|
||||
#define cpuplugd_info(fmt, ...) ({ \
|
||||
if (foreground == 1) \
|
||||
printf(fmt, ##__VA_ARGS__); \
|
||||
if (foreground == 0) \
|
||||
syslog(LOG_INFO, fmt, ##__VA_ARGS__); \
|
||||
})
|
||||
|
||||
#define cpuplugd_error(fmt, ...) ({ \
|
||||
if (foreground == 1) \
|
||||
fprintf(stderr, fmt, ##__VA_ARGS__); \
|
||||
if (foreground == 0) \
|
||||
syslog(LOG_ERR, fmt, ##__VA_ARGS__); \
|
||||
})
|
||||
|
||||
#define cpuplugd_debug(fmt, ...) ({ \
|
||||
if (debug) \
|
||||
cpuplugd_info(fmt, ##__VA_ARGS__); \
|
||||
})
|
||||
|
||||
#define cpuplugd_exit(fmt, ...) ({ \
|
||||
cpuplugd_error(fmt, ##__VA_ARGS__); \
|
||||
clean_up(); \
|
||||
})
|
||||
232
cpuplugd/daemon.c
Normal file
232
cpuplugd/daemon.c
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* Daemon 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 "cpuplugd.h"
|
||||
|
||||
const char *name = NAME;
|
||||
static const char *pid_file = PIDFILE;
|
||||
|
||||
const char *const usage =
|
||||
"Usage: %s [OPTIONS]\n"
|
||||
"\n"
|
||||
"Daemon to dynamically hotplug cpus and memory based on a set of rules\n"
|
||||
"Use OPTIONS described below.\n"
|
||||
"\n"
|
||||
"\t-c, --config CONFIGFILE Path to the configuration file\n"
|
||||
"\t-f, --foreground Run in foreground, do not detach\n"
|
||||
"\t-h, --help Print this help, then exit\n"
|
||||
"\t-v, --version Print version information, then exit\n"
|
||||
"\t-V, --verbose Provide more verbose output\n";
|
||||
|
||||
/*
|
||||
* Print command usage
|
||||
*/
|
||||
void print_usage(int is_error, char program_name[])
|
||||
{
|
||||
fprintf(is_error ? stderr : stdout, usage, program_name);
|
||||
exit(is_error ? 1 : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print command version
|
||||
*/
|
||||
void print_version()
|
||||
{
|
||||
printf("%s: Linux on System z CPU hotplug daemon version %s\n",
|
||||
name, RELEASE_STRING);
|
||||
printf("Copyright IBM Corp. 2007, 2017\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Store daemon's pid so it can be stopped
|
||||
*/
|
||||
void store_pid(void)
|
||||
{
|
||||
FILE *filp;
|
||||
|
||||
filp = fopen(pid_file, "w");
|
||||
if (!filp) {
|
||||
cpuplugd_error("cannot open pid file %s: %s\n", pid_file,
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
fprintf(filp, "%d\n", getpid());
|
||||
fclose(filp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that we don't try to start this daemon twice
|
||||
*/
|
||||
void check_if_started_twice()
|
||||
{
|
||||
FILE *filp;
|
||||
int pid, rc;
|
||||
|
||||
filp = fopen(pid_file, "r");
|
||||
if (filp) {
|
||||
rc = fscanf(filp, "%d", &pid);
|
||||
if (rc != 1) {
|
||||
cpuplugd_error("Reading pid file failed. Aborting!\n");
|
||||
exit(1);
|
||||
}
|
||||
cpuplugd_error("pid file %s still exists.\nThis might indicate "
|
||||
"that an instance of this daemon is already "
|
||||
"running.\n", pid_file);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up method
|
||||
*/
|
||||
void clean_up()
|
||||
{
|
||||
cpuplugd_info("terminated\n");
|
||||
remove(pid_file);
|
||||
remove(LOCKFILE);
|
||||
reactivate_cpus();
|
||||
if (memory)
|
||||
cleanup_cmm();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* End the deamon
|
||||
*/
|
||||
void kill_daemon(int UNUSED(a))
|
||||
{
|
||||
cpuplugd_info("shutting down\n");
|
||||
remove(pid_file);
|
||||
remove(LOCKFILE);
|
||||
reactivate_cpus();
|
||||
if (memory)
|
||||
cleanup_cmm();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reload the daemon (for lsb compliance)
|
||||
*/
|
||||
void reload_handler(int UNUSED(a))
|
||||
{
|
||||
reload_pending = 1;
|
||||
}
|
||||
|
||||
void reload_daemon()
|
||||
{
|
||||
unsigned int temp_history;
|
||||
long temp_mem;
|
||||
int temp_cpu;
|
||||
|
||||
cpuplugd_info("cpuplugd restarted\n");
|
||||
/*
|
||||
* Before we parse the configuration file again we have to save
|
||||
* the original values prior to startup. If we don't do this cpuplugd
|
||||
* will no longer know how many cpus the system had before the daemon
|
||||
* was started and therefor can't restore theres in case it is stopped
|
||||
*/
|
||||
temp_cpu = num_cpu_start;
|
||||
temp_mem = cmm_pagesize_start;
|
||||
temp_history = history_max;
|
||||
|
||||
/* clear varinfo before re-reading variables from config file */
|
||||
memset(varinfo, 0, varinfo_size);
|
||||
history_max = 1;
|
||||
parse_configfile(configfile);
|
||||
if (history_max > MAX_HISTORY)
|
||||
cpuplugd_exit("History depth %i exceeded maximum (%i)\n",
|
||||
history_max, MAX_HISTORY);
|
||||
if (history_max != temp_history) {
|
||||
free(meminfo);
|
||||
free(vmstat);
|
||||
free(cpustat);
|
||||
free(timestamps);
|
||||
setup_history();
|
||||
}
|
||||
check_config();
|
||||
|
||||
num_cpu_start = temp_cpu;
|
||||
cmm_pagesize_start = temp_mem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up for handling SIGTERM or SIGINT
|
||||
*/
|
||||
void handle_signals(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
|
||||
act.sa_flags = 0;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_handler = kill_daemon;
|
||||
if (sigaction(SIGTERM, &act, NULL) < 0) {
|
||||
cpuplugd_error("sigaction( SIGTERM, ... ) failed - reason %s\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
if (sigaction(SIGINT, &act, NULL) < 0) {
|
||||
cpuplugd_error("sigaction( SIGINT, ... ) failed - reason %s\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Signal handler for sighup. This is used to force the deamon to reload its
|
||||
* configuration file.
|
||||
* This feature is also required by a lsb compliant init script
|
||||
*/
|
||||
void handle_sighup(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
|
||||
act.sa_flags = 0;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_handler = reload_handler;
|
||||
if (sigaction(SIGHUP, &act, NULL) < 0) {
|
||||
cpuplugd_error("sigaction( SIGHUP, ... ) failed - reason %s\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we are running in an LPAR environment.
|
||||
* This functions return 1 if we run inside an lpar and 0 otherwise
|
||||
*/
|
||||
int check_lpar()
|
||||
{
|
||||
int rc;
|
||||
FILE *filp;
|
||||
size_t bytes_read;
|
||||
char buffer[2048];
|
||||
char *contains_vm;
|
||||
|
||||
rc = 0;
|
||||
filp = fopen("/proc/cpuinfo", "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("cannot open /proc/cpuinfo: %s\n",
|
||||
strerror(errno));
|
||||
bytes_read = fread(buffer, 1, sizeof(buffer) - 1, filp);
|
||||
if (bytes_read == 0)
|
||||
cpuplugd_exit("Reading /proc/cpuinfo failed: %s\n",
|
||||
strerror(errno));
|
||||
/* NUL-terminate the text */
|
||||
buffer[bytes_read] = '\0';
|
||||
contains_vm = strstr(buffer, "version = FF");
|
||||
if (contains_vm == NULL) {
|
||||
rc = 1;
|
||||
cpuplugd_debug("Detected System running in LPAR mode\n");
|
||||
} else
|
||||
cpuplugd_debug("Detected System running in z/VM mode\n");
|
||||
fclose(filp);
|
||||
return rc;
|
||||
}
|
||||
100
cpuplugd/getopt.c
Normal file
100
cpuplugd/getopt.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* Command line parsing
|
||||
*
|
||||
* 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 <getopt.h>
|
||||
#include "cpuplugd.h"
|
||||
|
||||
void print_usage(int is_error, char program_name[]);
|
||||
void print_version();
|
||||
int foreground;
|
||||
int debug;
|
||||
char *configfile;
|
||||
int cpu_idle_limit;
|
||||
|
||||
void parse_options(int argc, char **argv)
|
||||
{
|
||||
int config_file_specified = -1;
|
||||
const struct option long_options[] = {
|
||||
{ "help", no_argument, NULL, 'h'},
|
||||
{ "foreground", no_argument, NULL, 'f' },
|
||||
{ "config", required_argument, NULL, 'c' },
|
||||
{ "version", no_argument, NULL, 'v' },
|
||||
{ "verbose", no_argument, NULL, 'V' },
|
||||
{ NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
/* dont run without any argument */
|
||||
if (argc == 0 || argc == 1)
|
||||
print_usage(0, argv[0]);
|
||||
while (optind < argc) {
|
||||
int index = -1;
|
||||
struct option *opt = 0;
|
||||
int result = getopt_long(argc, argv, "hfc:vVm",
|
||||
long_options, &index);
|
||||
if (result == -1)
|
||||
break; /* end of list */
|
||||
switch (result) {
|
||||
case 'h':
|
||||
print_usage(0, argv[0]);
|
||||
break;
|
||||
case 'f':
|
||||
foreground = 1;
|
||||
break;
|
||||
case 'c':
|
||||
/*
|
||||
* This prevents -cbla and enforces the
|
||||
* user to specify -c bla
|
||||
*/
|
||||
if (strcmp(argv[optind-1], optarg) == 0) {
|
||||
configfile = optarg;
|
||||
config_file_specified = 1;
|
||||
} else {
|
||||
cpuplugd_error("Unrecognized option: %s\n",
|
||||
optarg);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
print_version();
|
||||
break;
|
||||
case 'V':
|
||||
debug = 1;
|
||||
break;
|
||||
case 0:
|
||||
/* all parameter that do not appear in the optstring */
|
||||
opt = (struct option *)&(long_options[index]);
|
||||
printf("'%s' was specified.",
|
||||
opt->name);
|
||||
if (opt->has_arg == required_argument)
|
||||
printf("Arg: <%s>", optarg);
|
||||
printf("\n");
|
||||
break;
|
||||
case '?':
|
||||
printf("Try '%s' --help' for more information.\n",
|
||||
argv[0]);
|
||||
exit(1);
|
||||
break;
|
||||
case -1:
|
||||
/*
|
||||
* We also run in this case if no argument was
|
||||
* specified
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
print_usage(0, argv[0]);
|
||||
}
|
||||
}
|
||||
if (config_file_specified == -1) {
|
||||
printf("You have to specify a configuration file!\n");
|
||||
printf("Try '%s' --help' for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
157
cpuplugd/info.c
Normal file
157
cpuplugd/info.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* /proc info 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 <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "cpuplugd.h"
|
||||
|
||||
/*
|
||||
* Return current load average and runnable processes based on /proc/loadavg
|
||||
*
|
||||
* Example: 0.20 0.18 0.12 1/80 11206
|
||||
*
|
||||
* The first three columns measure CPU utilization of the last 1, 5,
|
||||
* and 15 minute periods.
|
||||
* The fourth column shows the number of currently running processes
|
||||
* and the total number of processes.
|
||||
* The last column displays the last process ID used.
|
||||
*/
|
||||
void get_loadavg_runnable(double *loadavg, double *runnable)
|
||||
{
|
||||
FILE *filp;
|
||||
double dummy;
|
||||
int rc;
|
||||
|
||||
filp = fopen("/proc/loadavg", "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("cannot open kernel loadaverage "
|
||||
"statistics: %s\n", strerror(errno));
|
||||
rc = fscanf(filp, "%lf %lf %lf %lf/", loadavg, &dummy, &dummy,
|
||||
runnable);
|
||||
if (rc != 4)
|
||||
cpuplugd_exit("cannot parse kernel loadaverage "
|
||||
"statistics: %s\n", strerror(errno));
|
||||
fclose(filp);
|
||||
return;
|
||||
}
|
||||
|
||||
void proc_cpu_read(char *procinfo)
|
||||
{
|
||||
FILE *filp;
|
||||
unsigned int rc, onumcpus;
|
||||
unsigned long user, nice, system, idle, iowait, irq, softirq, steal,
|
||||
guest, guest_nice, total_ticks;
|
||||
double loadavg, runnable;
|
||||
|
||||
guest = guest_nice = 0; /* set to 0 if not present in kernel */
|
||||
filp = fopen("/proc/stat", "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("/proc/stat open failed: %s\n", strerror(errno));
|
||||
rc = fscanf(filp, "cpu %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld", &user,
|
||||
&nice, &system, &idle, &iowait, &irq, &softirq, &steal,
|
||||
&guest, &guest_nice);
|
||||
|
||||
get_loadavg_runnable(&loadavg, &runnable);
|
||||
onumcpus = get_num_online_cpus();
|
||||
total_ticks = user + nice + system + idle + iowait + irq + softirq +
|
||||
steal + guest + guest_nice;
|
||||
|
||||
rc = snprintf(procinfo, cpustat_size, "onumcpus %d\nloadavg %f\n"
|
||||
"runnable_proc %f\nuser %ld\nnice %ld\nsystem %ld\n"
|
||||
"idle %ld\niowait %ld\nirq %ld\nsoftirq %ld\nsteal %ld\n"
|
||||
"guest %ld\nguest_nice %ld\ntotal_ticks %ld\n",
|
||||
onumcpus, loadavg, runnable, user, nice, system, idle,
|
||||
iowait, irq, softirq, steal, guest, guest_nice,
|
||||
total_ticks);
|
||||
if (rc >= cpustat_size)
|
||||
cpuplugd_exit("cpustat buffer too small: need %d, have %ld "
|
||||
"(bytes)\n", rc, cpustat_size);
|
||||
fclose(filp);
|
||||
return;
|
||||
}
|
||||
|
||||
void proc_read(char *procinfo, char *path, unsigned long size)
|
||||
{
|
||||
size_t bytes_read;
|
||||
FILE *filp;
|
||||
|
||||
filp = fopen(path, "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("%s open failed: %s\n", path, strerror(errno));
|
||||
|
||||
bytes_read = fread(procinfo, 1, size, filp);
|
||||
if (bytes_read == 0)
|
||||
cpuplugd_exit("%s read failed\n", path);
|
||||
if (bytes_read == size)
|
||||
cpuplugd_exit("procinfo buffer too small for %s\n", path);
|
||||
|
||||
procinfo[bytes_read] = '\0';
|
||||
fclose(filp);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long proc_read_size(char *path)
|
||||
{
|
||||
FILE *filp;
|
||||
char buf[PROCINFO_LINE];
|
||||
char *linep, *linep_offset;
|
||||
unsigned long size;
|
||||
|
||||
filp = fopen(path, "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("%s open failed: %s\n", path, strerror(errno));
|
||||
|
||||
size = 0;
|
||||
while ((linep = fgets(buf, sizeof(buf), filp))) {
|
||||
if (!(linep_offset = strchr(linep, '\n')))
|
||||
cpuplugd_exit("buf too small for line\n");
|
||||
size = size + linep_offset - linep + 1;
|
||||
}
|
||||
fclose(filp);
|
||||
return size;
|
||||
}
|
||||
|
||||
double get_proc_value(char *procinfo, char *name, char separator)
|
||||
{
|
||||
char buf[PROCINFO_LINE];
|
||||
char *proc_offset;
|
||||
unsigned long proc_length, name_length;
|
||||
double value;
|
||||
int found;
|
||||
|
||||
value = -1;
|
||||
found = 0;
|
||||
name_length = strlen(name);
|
||||
while ((proc_offset = strchr(procinfo, separator))) {
|
||||
proc_length = proc_offset - procinfo;
|
||||
/*
|
||||
* proc_read_size() made sure that proc_length < PROCINFO_LINE
|
||||
*/
|
||||
memcpy(buf, procinfo, proc_length);
|
||||
buf[proc_length] = '\0';
|
||||
procinfo = proc_offset + 1;
|
||||
if (strncmp(buf, name, MAX(proc_length, name_length)) == 0) {
|
||||
errno = 0;
|
||||
value = strtod(procinfo, NULL);
|
||||
if (errno)
|
||||
cpuplugd_exit("strtod failed\n");
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
proc_offset = strchr(procinfo, '\n');
|
||||
procinfo = proc_offset + 1;
|
||||
}
|
||||
if (!found)
|
||||
cpuplugd_exit("Symbol %s not found, check your config file\n",
|
||||
name);
|
||||
return value;
|
||||
}
|
||||
485
cpuplugd/main.c
Normal file
485
cpuplugd/main.c
Normal file
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
64
cpuplugd/man/cpuplugd.8
Normal file
64
cpuplugd/man/cpuplugd.8
Normal file
@@ -0,0 +1,64 @@
|
||||
.\" Copyright 2017 IBM Corp.
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH CPUPLUGD 8 "May 2011" "s390-tools"
|
||||
.
|
||||
.SH NAME
|
||||
cpuplugd \- Linux on System z CPU and memory hotplug daemon
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
.B cpuplugd
|
||||
.RI [ OPTIONS ]
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
The cpuplugd daemon dynamically enables and disables CPUs and increases or
|
||||
decreases the cooperative memory management (CMM) page pool based on a set of
|
||||
rules.
|
||||
|
||||
When the daemon is stopped, the size of the CMM page pool and the number
|
||||
of active CPUs are reset to the values they had before the cpuplugd was started.
|
||||
|
||||
This program can be used to control the number of CPUs for Linux on z/VM
|
||||
and for Linux in LPAR mode. The memory hotplug feature (CMM page pool) applies
|
||||
to Linux on z/VM only.
|
||||
.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-c\fP or \fB\-\-config\fP \fI<configuration file>\fP
|
||||
Specify the absolute path to the configuration file. This option is mandatory.
|
||||
The default configuration file can be found in /etc/cpuplugd.conf.
|
||||
.
|
||||
.TP
|
||||
\fB\-f\fP or \fB\-\-foreground\fP
|
||||
Run in the foreground and not as daemon. If this option is
|
||||
omitted, the program runs in the background.
|
||||
.
|
||||
.TP
|
||||
\fB\-h\fP or \fB\-\-help\fP
|
||||
Print usage message and exit.
|
||||
.
|
||||
.TP
|
||||
\fB\-v\fP or \fB\-\-version\fP
|
||||
Print Version information and exit.
|
||||
.
|
||||
.TP
|
||||
\fB\-V\fP or \fB\-\-verbose\fP
|
||||
Print verbose messages to stdout (when running in foreground)
|
||||
or to syslog otherwise.
|
||||
This options is mainly used for debugging purposes.
|
||||
.
|
||||
.SH EXAMPLES
|
||||
To test a setup start cpuplugd in foreground mode using verbose output:
|
||||
.br
|
||||
.RS 4
|
||||
cpuplugd \-V \-f \-c /etc/cpuplugd.conf
|
||||
.RE
|
||||
|
||||
For daemon mode, start cpuplugd from an init script as follows:
|
||||
.br
|
||||
.RS 4
|
||||
cpuplugd \-c /etc/cpuplugd.conf
|
||||
.RE
|
||||
.SH SEE ALSO
|
||||
.BR cpuplugd.conf (5)
|
||||
284
cpuplugd/man/cpuplugd.conf.5
Normal file
284
cpuplugd/man/cpuplugd.conf.5
Normal file
@@ -0,0 +1,284 @@
|
||||
.\" Copyright 2017 IBM Corp.
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
.TH CPUPLUGD.CONF 5 "May 2011" "s390-tools"
|
||||
.
|
||||
.SH NAME
|
||||
cpuplugd.conf \- Configuration file for the Linux on System z CPU and memory
|
||||
hotplug daemon
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
The cpuplugd.conf configuration file contains the configuration information for
|
||||
the Linux for System z CPU and memory hotplug daemon.
|
||||
|
||||
Use this file to specify rules for enabling or disabling CPUs and for adding
|
||||
or removing memory.
|
||||
.
|
||||
.SS "CPU hotplug"
|
||||
CPUs can be enabled and disabled through a sysfs interface.
|
||||
The status file for a CPU, here CPU number 16 (counting starts at 0),
|
||||
is /sys/devices/system/cpu/cpu15/online.
|
||||
|
||||
Writing a 0 to this file disables the CPU. Writing a 1 enables the CPU.
|
||||
.
|
||||
.SS "Memory hotplug"
|
||||
The rules that add or remove memory use the cooperative memory management
|
||||
(CMM) feature.
|
||||
|
||||
CMM is a mechanism to reduce the memory available to Linux instances that run
|
||||
as guests of z/VM.
|
||||
CMM allocates pages to a dynamic page pool not available to Linux.
|
||||
A diagnose code indicates to z/VM that the pages in the page pool are out of
|
||||
use. z/VM can then immediately reuse these pages for other guests.
|
||||
.
|
||||
.SS "Layout of the configuration file"
|
||||
The configuration file contains variables specifying static numbers or
|
||||
expressions. They are of the format \fB<variable>="<value>"\fP and they
|
||||
need to be specified within one line. Expressions can be specified
|
||||
to calculate algebraic values or to define boolean rules, which determine
|
||||
when a hotplug/hotunplug action should be taken. The maximum valid line
|
||||
length is 2048 characters.
|
||||
|
||||
There are case-insensitive pre-defined and case-sensitive user-defined
|
||||
variables. The configuration file must include specifications for all
|
||||
pre-defined variables. If a variable is not set, the hotplug function it
|
||||
applies to, CPU or memory, is disabled.
|
||||
The only exception to this rule is CMM_DEC, which defaults to the setting
|
||||
for CMM_INC if omitted. If a pre-defined variable is set more than once, only
|
||||
the last occurrence is used. User-defined variables must not be set more than
|
||||
once.
|
||||
.
|
||||
.SS "Hotplug rules"
|
||||
Set these pre-defined variables to an expression that resolves to a boolean
|
||||
value (true or false). These variables trigger hotplug actions. Setting a
|
||||
variable to "0" disables the action.
|
||||
.
|
||||
.RS 2
|
||||
.IP "-" 2
|
||||
\fBHOTPLUG\fP - used to enable CPUs
|
||||
.IP "-" 2
|
||||
\fBHOTUNPLUG\fP - used to disable CPUs
|
||||
.IP "-" 2
|
||||
\fBMEMPLUG\fP - used to increase the available memory
|
||||
.IP "-" 2
|
||||
\fBMEMUNPLUG\fP - used to decrease the amount of memory
|
||||
.RE
|
||||
.PP
|
||||
The following operators can be used in a hotplug rule expression:
|
||||
.br
|
||||
.RS 2
|
||||
.B + * ( ) / - < >
|
||||
.RE
|
||||
.br
|
||||
Furthermore, the boolean operators \fB & \fP (and) \fB|\fP (or) and \fB!\fP
|
||||
(not) can be used.
|
||||
|
||||
If both HOTPLUG and HOTUNPLUG evaluate to true, only the HOTPLUG action is
|
||||
triggered. If both MEMPLUG and MEMUNPLUG evaluate to true, only the MEMPLUG
|
||||
action is triggered.
|
||||
.
|
||||
.SS "Pre-defined static variables"
|
||||
The following pre-defined variables can be set only to a static, positive,
|
||||
numeric value:
|
||||
.
|
||||
.RS 2
|
||||
.IP "-" 2
|
||||
\fBCPU_MIN\fP - the minimum number of CPUs to keep online (> 0)
|
||||
.IP "-" 2
|
||||
\fBCPU_MAX\fP - the maximum number of CPUs to enable (>= 0)
|
||||
.IP "-" 2
|
||||
\fBUPDATE\fP - the interval at which cpuplugd evaluates the rules (in seconds,
|
||||
> 0)
|
||||
.IP "-" 2
|
||||
\fBCMM_MIN\fP - the minimum size of the CMM page pool (>= 0)
|
||||
.IP "-" 2
|
||||
\fBCMM_MAX\fP - the maximum size of the CMM page pool (>= 0)
|
||||
.RE
|
||||
.PP
|
||||
If the value of CPU_MAX is 0, the overall number of CPUs found in this system
|
||||
is used as the maximum.
|
||||
.
|
||||
.SS "Pre-defined dynamic variables"
|
||||
The following pre-defined variables can either be set to a static value or to an
|
||||
algebraic expression:
|
||||
.
|
||||
.RS 2
|
||||
.IP "-" 2
|
||||
\fBCMM_INC\fP - the amount of pages by which the CMM page pool is increased
|
||||
if the MEMUNPLUG rule is matched (available system memory is decreased).
|
||||
.IP "-" 2
|
||||
\fBCMM_DEC\fP - the amount of pages by which the CMM page pool is decreased
|
||||
if the MEMPLUG rule is matched (available system memory is increased).
|
||||
.RE
|
||||
.PP
|
||||
The following operators can be used in a dynamic variable expression:
|
||||
.br
|
||||
.RS 2
|
||||
.B + * ( ) / - < >
|
||||
.RE
|
||||
.br
|
||||
.
|
||||
.SS "User-defined variables"
|
||||
You can specify complex calculations as user-defined variables, which can then
|
||||
be used in expressions. User-defined variables are case-sensitive and must not
|
||||
match a pre-defined variable or keyword. In the configuration file, definitions
|
||||
for user-defined variables must precede their use in expressions.
|
||||
|
||||
Variable names consist of alphanumeric characters (a-z,A-Z,0-9) and
|
||||
the "_" character, see section \fB"EXAMPLES"\fP for an example (pgscanrate). The
|
||||
maximum name
|
||||
length for a variable is 128 characters, and the maximum total size for all
|
||||
user-defined variables (names + values) is 4096 characters.
|
||||
.
|
||||
.SS "Keywords for CPU hotplug rules"
|
||||
The \fBHOTPLUG\fP and \fBHOTUNPLUG\fP rules can contain the following
|
||||
pre-defined keywords:
|
||||
.
|
||||
.RS 2
|
||||
.IP "-" 2
|
||||
\fBloadavg\fP - the current load average
|
||||
.IP "-" 2
|
||||
\fBonumcpus\fP - the current number of CPUs which are online
|
||||
.IP "-" 2
|
||||
\fBrunnable_proc\fP - the current amount of runnable processes
|
||||
.IP "-" 2
|
||||
\fBuser\fP - the current CPU user percentage
|
||||
.IP "-" 2
|
||||
\fBnice\fP - the current CPU nice percentage
|
||||
.IP "-" 2
|
||||
\fBsystem\fP - the current CPU system percentage
|
||||
.IP "-" 2
|
||||
\fBidle\fP - the current CPU idle percentage
|
||||
.IP "-" 2
|
||||
\fBiowait\fP - the current CPU iowait percentage
|
||||
.IP "-" 2
|
||||
\fBirq\fP - the current CPU irq percentage
|
||||
.IP "-" 2
|
||||
\fBsoftirq\fP - the current CPU softirq percentage
|
||||
.IP "-" 2
|
||||
\fBsteal\fP - the current CPU steal percentage
|
||||
.IP "-" 2
|
||||
\fBguest\fP - the current CPU guest percentage (depends on kernel version: if not reported in /proc/stat, this is set to 0)
|
||||
.IP "-" 2
|
||||
\fBguest_nice\fP - the current CPU guest_nice percentage (depends on kernel version: if not reported in /proc/stat, this is set to 0)
|
||||
.IP "-" 2
|
||||
\fBcpustat.<name>\fP - data from /proc/stat and /proc/loadavg
|
||||
.IP "-" 2
|
||||
\fBtime\fP - floating point timestamp in "seconds.microseconds" since the Unix
|
||||
Epoch (1970-01-01 00:00:00 +0000 (UTC))
|
||||
.RE
|
||||
.PP
|
||||
The percentage values are accumulated over all online CPUs, so they can vary
|
||||
between 0 and (100 * \fBonumcpus\fP).
|
||||
|
||||
CPU usage data from /proc/stat and /proc/loadavg is accessible by
|
||||
specifying \fBcpustat.<name>\fP, where \fB<name>\fP can be any of the keywords
|
||||
described above, plus \fBtotal_ticks\fP. In this case, \fBloadavg\fP,
|
||||
\fBonumcpus\fP and \fBrunnable_proc\fP
|
||||
provide the same values as the pre-defined keywords, while the others refer
|
||||
to the raw timer ticks as reported by /proc/stat, not the percentage.
|
||||
For example, \fBcpustat.idle\fP reports the timer ticks spent in idle since
|
||||
system start, and \fBcpustat.total_ticks\fP indicates the sum of all reported
|
||||
timer ticks, which can be useful for user-defined percentage calculations.
|
||||
.
|
||||
.SS "Keywords for memory hotplug rules"
|
||||
The \fBMEMPLUG\fP and \fBMEMUNPLUG\fP rules can contain the following
|
||||
pre-defined keywords:
|
||||
.
|
||||
.RS 2
|
||||
.IP "-" 2
|
||||
\fBapcr\fP - the amount of page cache operations, i.e. pgpin + pgpout from
|
||||
/proc/vmstat (in 512 byte blocks / second)
|
||||
.IP "-" 2
|
||||
\fBfreemem\fP - the amount of free memory (in megabytes)
|
||||
.IP "-" 2
|
||||
\fBswaprate\fP - the number of swap operations, i.e. pswpin + pswpout from
|
||||
/proc/vmstat (in pages / second)
|
||||
.IP "-" 2
|
||||
\fBmeminfo.<name>\fP - any value from /proc/meminfo
|
||||
.IP "-" 2
|
||||
\fBvmstat.<name>\fP - any value from /proc/vmstat
|
||||
.IP "-" 2
|
||||
\fBtime\fP - floating point timestamp in "seconds.microseconds" since the Unix
|
||||
Epoch (1970-01-01 00:00:00 +0000 (UTC))
|
||||
.RE
|
||||
.PP
|
||||
All values from /proc/meminfo and /proc/vmstat can be used in an expression
|
||||
by specifying \fBmeminfo.<name>\fP or \fBvmstat.<name>\fP, where \fB<name>\fP
|
||||
matches a symbol name reported by /proc/meminfo or /proc/vmstat (case
|
||||
sensitive), e.g. \fBmeminfo.MemTotal\fP.
|
||||
.
|
||||
.SS "History function"
|
||||
There is a history function for the following keywords:
|
||||
.
|
||||
.RS 2
|
||||
.IP "-" 2
|
||||
\fBcpustat.<name>\fP - data from /proc/stat and /proc/loadavg
|
||||
.IP "-" 2
|
||||
\fBmeminfo.<name>\fP - any value from /proc/meminfo
|
||||
.IP "-" 2
|
||||
\fBvmstat.<name>\fP - any value from /proc/vmstat
|
||||
.IP "-" 2
|
||||
\fBtime\fP - floating point timestamp in "seconds.microseconds" since the Unix
|
||||
Epoch (1970-01-01 00:00:00 +0000 (UTC))
|
||||
.RE
|
||||
.PP
|
||||
The history levels can be accessed by appending \fB[<history level>]\fP to the
|
||||
name, where \fB<history level>\fP indicates the amount of past intervals where
|
||||
the value was gathered. [0] means the current interval (the [0] can be omitted
|
||||
in this case), [1] means the previous interval, [2] means two intervals ago,
|
||||
and so on. The history limit is 100. For example, \fBcpustat.system[1]\fP would
|
||||
indicate the system value from /proc/stat at the previous interval, while
|
||||
\fBvmstat.pgpgin\fP and \fBvmstat.pgpgin[0]\fP would both mean the current
|
||||
pgpgin value from /proc/vmstat.
|
||||
|
||||
The \fBtime\fP keyword and its history values can be used to calculate values
|
||||
dependent on time intervals, see section \fB"EXAMPLES"\fP for an example
|
||||
(pgscanrate).
|
||||
.
|
||||
.SH EXAMPLES
|
||||
A complete configuration file could look like this:
|
||||
|
||||
.nf
|
||||
------------------------------ config file start ------------------------------
|
||||
UPDATE="5"
|
||||
CPU_MIN="2"
|
||||
CPU_MAX="5"
|
||||
CMM_MIN="0"
|
||||
CMM_MAX="131072" # 512 MB
|
||||
|
||||
pgscan_k="vmstat.pgscan_kswapd_dma + vmstat.pgscan_kswapd_normal + vmstat.pgscan_kswapd_movable"
|
||||
pgscan_d="vmstat.pgscan_direct_dma + vmstat.pgscan_direct_normal + vmstat.pgscan_direct_movable"
|
||||
pgscan_k1="vmstat.pgscan_kswapd_dma[1] + vmstat.pgscan_kswapd_normal[1] + vmstat.pgscan_kswapd_movable[1]"
|
||||
pgscan_d1="vmstat.pgscan_direct_dma[1] + vmstat.pgscan_direct_normal[1] + vmstat.pgscan_direct_movable[1]"
|
||||
pgscanrate="(pgscan_k + pgscan_d - pgscan_k1 - pgscan_d1) / (time - time[1])"
|
||||
cache="meminfo.Cached + meminfo.Buffers"
|
||||
|
||||
# CMM_INC: 10% of free memory + cache, in 4K pages
|
||||
CMM_INC="(meminfo.MemFree + cache) / 40"
|
||||
# CMM_DEC: 10% of total memory in 4K pages
|
||||
CMM_DEC="meminfo.MemTotal / 40"
|
||||
|
||||
HOTPLUG = "(loadavg > onumcpus + 0.75) & (idle < 10.0)"
|
||||
HOTUNPLUG = "(loadavg < onumcpus - 0.25) | (idle > 50)"
|
||||
|
||||
# Plug memory if page scan rate is above 20 pages / sec
|
||||
MEMPLUG = "pgscanrate > 20"
|
||||
# Unplug memory while free memory is above 10% of total memory, or cache uses
|
||||
# more than 50% of total memory
|
||||
MEMUNPLUG = "(meminfo.MemFree > meminfo.MemTotal / 10) | (cache > meminfo.MemTotal / 2)"
|
||||
------------------------------ config file end ------------------------------
|
||||
.fi
|
||||
|
||||
The example includes multiple user-defined variables to calculate the page scan
|
||||
rate with values from /proc/vmstat, as well as the cache size.
|
||||
|
||||
\fBAttention:\fP Do not use these example rules on production systems. The
|
||||
rules have been designed to illustrate the configuration file syntax and are
|
||||
not suitable for actually governing hotplug actions. Useful rules differ
|
||||
considerably depending on the workload, resources, and requirements of the
|
||||
system they are designed for.
|
||||
.
|
||||
.SH SEE ALSO
|
||||
.BR cpuplugd (8)
|
||||
82
cpuplugd/mem.c
Normal file
82
cpuplugd/mem.c
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* cmm 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 "cpuplugd.h"
|
||||
|
||||
/*
|
||||
* The cmm_pages value defines the size of the balloon of blocked memory.
|
||||
* Increasing the value is removing memory from Linux, which is an memunplug.
|
||||
* Decreasing the value is adding memory back to Linux, which is memplug.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Set the value of cmm_pages
|
||||
*/
|
||||
void set_cmm_pages(long pages)
|
||||
{
|
||||
FILE *filp;
|
||||
|
||||
filp = fopen("/proc/sys/vm/cmm_pages", "w");
|
||||
if (!filp)
|
||||
cpuplugd_exit("Cannot open /proc/sys/vm/cmmpages: %s\n",
|
||||
strerror(errno));
|
||||
cpuplugd_debug("changing number of pages permanently reserved to %ld\n",
|
||||
pages);
|
||||
fprintf(filp, "%ld\n", pages);
|
||||
fclose(filp);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read number of pages permanently reserved
|
||||
*/
|
||||
long get_cmmpages_size()
|
||||
{
|
||||
FILE *filp;
|
||||
long size;
|
||||
int rc;
|
||||
|
||||
filp = fopen("/proc/sys/vm/cmm_pages", "r");
|
||||
if (!filp)
|
||||
cpuplugd_exit("Cannot open /proc/sys/vm/cmm_pages: %s\n",
|
||||
strerror(errno));
|
||||
rc = fscanf(filp, "%ld", &size);
|
||||
if (rc == 0)
|
||||
cpuplugd_exit("Can not read /proc/sys/vm/cmm_pages: %s\n",
|
||||
strerror(errno));
|
||||
fclose(filp);
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset cmm pagesize to value we found prior to daemon startup
|
||||
*/
|
||||
void cleanup_cmm()
|
||||
{
|
||||
set_cmm_pages(cmm_pagesize_start);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to check if the cmm kernel module is loaded and the required
|
||||
* files below /proc exit
|
||||
*/
|
||||
int check_cmmfiles(void)
|
||||
{
|
||||
FILE *filp;
|
||||
|
||||
filp = fopen("/proc/sys/vm/cmm_pages", "r");
|
||||
if (!filp)
|
||||
return -1;
|
||||
fclose(filp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
550
cpuplugd/terms.c
Normal file
550
cpuplugd/terms.c
Normal file
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* cpuplugd - Linux for System z Hotplug Daemon
|
||||
*
|
||||
* Term parsing
|
||||
*
|
||||
* 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 "cpuplugd.h"
|
||||
|
||||
static enum op_prio op_prio_table[] =
|
||||
{
|
||||
[OP_NEG] = OP_PRIO_ADD,
|
||||
[OP_GREATER] = OP_PRIO_CMP,
|
||||
[OP_LESSER] = OP_PRIO_CMP,
|
||||
[OP_PLUS] = OP_PRIO_ADD,
|
||||
[OP_MINUS] = OP_PRIO_ADD,
|
||||
[OP_MULT] = OP_PRIO_MULT,
|
||||
[OP_DIV] = OP_PRIO_MULT,
|
||||
[OP_AND] = OP_PRIO_AND,
|
||||
[OP_OR] = OP_PRIO_OR,
|
||||
};
|
||||
|
||||
static void free_term(struct term *fn)
|
||||
{
|
||||
if (!fn)
|
||||
return;
|
||||
switch (fn->op) {
|
||||
case OP_SYMBOL_LOADAVG:
|
||||
case OP_SYMBOL_RUNABLE:
|
||||
case OP_SYMBOL_CPUS:
|
||||
case OP_SYMBOL_USER:
|
||||
case OP_SYMBOL_NICE:
|
||||
case OP_SYMBOL_SYSTEM:
|
||||
case OP_SYMBOL_IDLE:
|
||||
case OP_SYMBOL_IOWAIT:
|
||||
case OP_SYMBOL_IRQ:
|
||||
case OP_SYMBOL_SOFTIRQ:
|
||||
case OP_SYMBOL_STEAL:
|
||||
case OP_SYMBOL_GUEST:
|
||||
case OP_SYMBOL_GUEST_NICE:
|
||||
case OP_CONST:
|
||||
free(fn);
|
||||
break;
|
||||
case OP_NEG:
|
||||
case OP_NOT:
|
||||
free_term(fn->left);
|
||||
free(fn);
|
||||
break;
|
||||
case OP_GREATER:
|
||||
case OP_LESSER:
|
||||
case OP_PLUS:
|
||||
case OP_MINUS:
|
||||
case OP_MULT:
|
||||
case OP_DIV:
|
||||
case OP_AND:
|
||||
free_term(fn->left);
|
||||
free_term(fn->right);
|
||||
free(fn);
|
||||
break;
|
||||
case OP_OR:
|
||||
free_term(fn->left);
|
||||
free_term(fn->right);
|
||||
free(fn);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void print_term(struct term *fn)
|
||||
{
|
||||
switch (fn->op) {
|
||||
case OP_SYMBOL_LOADAVG:
|
||||
printf("loadavg");
|
||||
break;
|
||||
case OP_SYMBOL_RUNABLE:
|
||||
printf("runnable_proc");
|
||||
break;
|
||||
case OP_SYMBOL_CPUS:
|
||||
printf("onumcpus");
|
||||
break;
|
||||
case OP_SYMBOL_USER:
|
||||
printf("user");
|
||||
break;
|
||||
case OP_SYMBOL_NICE:
|
||||
printf("nice");
|
||||
break;
|
||||
case OP_SYMBOL_SYSTEM:
|
||||
printf("system");
|
||||
break;
|
||||
case OP_SYMBOL_IDLE:
|
||||
printf("idle");
|
||||
break;
|
||||
case OP_SYMBOL_IOWAIT:
|
||||
printf("iowait");
|
||||
break;
|
||||
case OP_SYMBOL_IRQ:
|
||||
printf("irq");
|
||||
break;
|
||||
case OP_SYMBOL_SOFTIRQ:
|
||||
printf("softirq");
|
||||
break;
|
||||
case OP_SYMBOL_STEAL:
|
||||
printf("steal");
|
||||
break;
|
||||
case OP_SYMBOL_GUEST:
|
||||
printf("guest");
|
||||
break;
|
||||
case OP_SYMBOL_GUEST_NICE:
|
||||
printf("guest_nice");
|
||||
break;
|
||||
case OP_SYMBOL_SWAPRATE:
|
||||
printf("swaprate");
|
||||
break;
|
||||
case OP_SYMBOL_FREEMEM:
|
||||
printf("freemem");
|
||||
break;
|
||||
case OP_SYMBOL_APCR:
|
||||
printf("apcr");
|
||||
break;
|
||||
case OP_SYMBOL_MEMINFO:
|
||||
printf("meminfo.%s[%u]", fn->proc_name, fn->index);
|
||||
break;
|
||||
case OP_SYMBOL_VMSTAT:
|
||||
printf("vmstat.%s[%u]", fn->proc_name, fn->index);
|
||||
break;
|
||||
case OP_SYMBOL_CPUSTAT:
|
||||
printf("cpustat.%s[%u]", fn->proc_name, fn->index);
|
||||
break;
|
||||
case OP_SYMBOL_TIME:
|
||||
printf("time[%u]", fn->index);
|
||||
break;
|
||||
case OP_CONST:
|
||||
printf("%f", fn->value);
|
||||
break;
|
||||
case OP_NEG:
|
||||
printf("-(");
|
||||
print_term(fn->left);
|
||||
printf(")");
|
||||
break;
|
||||
case OP_NOT:
|
||||
printf("!(");
|
||||
print_term(fn->left);
|
||||
printf(")");
|
||||
break;
|
||||
case OP_PLUS:
|
||||
case OP_MINUS:
|
||||
case OP_MULT:
|
||||
case OP_DIV:
|
||||
case OP_AND:
|
||||
case OP_OR:
|
||||
case OP_GREATER:
|
||||
case OP_LESSER:
|
||||
printf("(");
|
||||
print_term(fn->left);
|
||||
switch (fn->op) {
|
||||
case OP_AND:
|
||||
printf(") & (");
|
||||
break;
|
||||
case OP_OR:
|
||||
printf(") | (");
|
||||
break;
|
||||
case OP_GREATER:
|
||||
printf(") > (");
|
||||
break;
|
||||
case OP_LESSER:
|
||||
printf(") < (");
|
||||
break;
|
||||
case OP_PLUS:
|
||||
printf(") + (");
|
||||
break;
|
||||
case OP_MINUS:
|
||||
printf(") - (");
|
||||
break;
|
||||
case OP_MULT:
|
||||
printf(") * (");
|
||||
break;
|
||||
case OP_DIV:
|
||||
printf(") / (");
|
||||
break;
|
||||
// TODO OP_CONST, OP_SYMBOL_LOADAVG, ... possible here???
|
||||
case OP_CONST:
|
||||
printf("%f", fn->value);
|
||||
break;
|
||||
case OP_SYMBOL_LOADAVG:
|
||||
case OP_SYMBOL_RUNABLE:
|
||||
case OP_SYMBOL_CPUS:
|
||||
case OP_SYMBOL_USER:
|
||||
case OP_SYMBOL_NICE:
|
||||
case OP_SYMBOL_SYSTEM:
|
||||
case OP_SYMBOL_IDLE:
|
||||
case OP_SYMBOL_IOWAIT:
|
||||
case OP_SYMBOL_IRQ:
|
||||
case OP_SYMBOL_SOFTIRQ:
|
||||
case OP_SYMBOL_STEAL:
|
||||
case OP_SYMBOL_GUEST:
|
||||
case OP_SYMBOL_GUEST_NICE:
|
||||
case OP_SYMBOL_APCR:
|
||||
case OP_SYMBOL_SWAPRATE:
|
||||
case OP_SYMBOL_FREEMEM:
|
||||
case OP_SYMBOL_MEMINFO: // TODO use default: ???
|
||||
case OP_SYMBOL_VMSTAT: // TODO use default: ???
|
||||
case OP_SYMBOL_CPUSTAT: // TODO use default: ???
|
||||
case OP_SYMBOL_TIME: // TODO use default: ???
|
||||
case OP_NEG:
|
||||
case OP_NOT:
|
||||
case VAR_LOAD:
|
||||
case VAR_RUN:
|
||||
case VAR_ONLINE:
|
||||
break;
|
||||
}
|
||||
print_term(fn->right);
|
||||
printf(")");
|
||||
break;
|
||||
case VAR_LOAD:
|
||||
case VAR_RUN:
|
||||
case VAR_ONLINE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct term *parse_var_term(char **p)
|
||||
{
|
||||
char *s, *var_rvalue;
|
||||
struct term *fn;
|
||||
unsigned int length;
|
||||
char var_name[MAX_VARNAME + 1];
|
||||
|
||||
s = *p;
|
||||
length = 0;
|
||||
fn = NULL;
|
||||
while (isalnum(*s) || *s == '_') {
|
||||
var_name[length] = *s;
|
||||
length++;
|
||||
s++;
|
||||
if (length > MAX_VARNAME)
|
||||
cpuplugd_exit("Variable name too long (max. length is "
|
||||
"%i chars): %s\n", MAX_VARNAME, *p);
|
||||
}
|
||||
var_name[length] = '\0';
|
||||
var_rvalue = get_var_rvalue(var_name);
|
||||
if (var_rvalue) {
|
||||
fn = parse_term(&var_rvalue, OP_PRIO_NONE);
|
||||
if (var_rvalue[0] != '\n')
|
||||
cpuplugd_exit("parsing error at %s, position: %s\n",
|
||||
var_name, var_rvalue);
|
||||
*p = s;
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
struct term *parse_term(char **p, enum op_prio prio)
|
||||
{
|
||||
struct term *fn, *new;
|
||||
enum operation op;
|
||||
char *s, *endptr;
|
||||
double value;
|
||||
unsigned int length, i, index;
|
||||
|
||||
s = *p;
|
||||
fn = NULL;
|
||||
if (*s == '-') {
|
||||
s++;
|
||||
fn = malloc(sizeof(struct term));
|
||||
if (fn == NULL)
|
||||
goto out_error;
|
||||
if (isdigit(*s)) {
|
||||
value = 0;
|
||||
length = 0;
|
||||
sscanf(s, "%lf%n", &value, &length);
|
||||
fn->op = OP_CONST;
|
||||
fn->value = -value;
|
||||
s += length;
|
||||
} else {
|
||||
fn->op = OP_NEG;
|
||||
fn->left = parse_term(&s, prio);
|
||||
if (fn->left == NULL)
|
||||
goto out_error;
|
||||
}
|
||||
} else if (*s == '!') {
|
||||
s++;
|
||||
fn = malloc(sizeof(struct term));
|
||||
if (fn == NULL)
|
||||
goto out_error;
|
||||
fn->op = OP_NOT;
|
||||
fn->left = parse_term(&s, prio);
|
||||
if (fn->left == NULL)
|
||||
goto out_error;
|
||||
} else if (isdigit(*s)) {
|
||||
value = 0;
|
||||
length = 0;
|
||||
sscanf(s, "%lf%n", &value, &length);
|
||||
for (i = 0; i < length; i++)
|
||||
s++;
|
||||
fn = malloc(sizeof(struct term));
|
||||
if (fn == NULL)
|
||||
goto out_error;
|
||||
fn->op = OP_CONST;
|
||||
fn->value = value;
|
||||
} else if (*s == '(') {
|
||||
s++;
|
||||
fn = parse_term(&s, OP_PRIO_NONE);
|
||||
if (fn == NULL || *s != ')')
|
||||
goto out_error;
|
||||
s++;
|
||||
} else {
|
||||
/* Check for variable name */
|
||||
fn = parse_var_term(&s);
|
||||
if (fn == NULL) {
|
||||
for (i = 0; i < sym_names_count; i++)
|
||||
if (strncmp(s, sym_names[i].name,
|
||||
strlen(sym_names[i].name)) == 0)
|
||||
break;
|
||||
if (i >= sym_names_count)
|
||||
/* Term doesn't make sense. */
|
||||
goto out_error;
|
||||
/*
|
||||
* Parse meminfo/vmstat/cpustat with optional history
|
||||
* index [x]
|
||||
*/
|
||||
fn = malloc(sizeof(struct term));
|
||||
if (fn == NULL)
|
||||
goto out_error;
|
||||
fn->op = sym_names[i].symop;
|
||||
s += strlen(sym_names[i].name);
|
||||
length = 0;
|
||||
if (fn->op == OP_SYMBOL_MEMINFO ||
|
||||
fn->op == OP_SYMBOL_VMSTAT ||
|
||||
fn->op == OP_SYMBOL_CPUSTAT) {
|
||||
while (isalpha(s[length]) || s[length] == '_')
|
||||
length++;
|
||||
fn->proc_name = malloc(length + 1);
|
||||
if (fn->proc_name == NULL)
|
||||
goto out_error;
|
||||
strncpy(fn->proc_name, s, length);
|
||||
fn->proc_name[length] = '\0';
|
||||
}
|
||||
if (fn->op == OP_SYMBOL_MEMINFO ||
|
||||
fn->op == OP_SYMBOL_VMSTAT ||
|
||||
fn->op == OP_SYMBOL_CPUSTAT ||
|
||||
fn->op == OP_SYMBOL_TIME) {
|
||||
if (s[length] == '[') {
|
||||
length++;
|
||||
if (!isdigit(s[length]))
|
||||
goto out_error;
|
||||
index = strtol(s + length, &endptr, 10);
|
||||
length = endptr - s;
|
||||
if (s[length] != ']')
|
||||
goto out_error;
|
||||
fn->index = index;
|
||||
if (history_max < index)
|
||||
history_max = index;
|
||||
length++;
|
||||
}
|
||||
s += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (1) {
|
||||
switch (*s) {
|
||||
case '>':
|
||||
op = OP_GREATER;
|
||||
break;
|
||||
case '<':
|
||||
op = OP_LESSER;
|
||||
break;
|
||||
case '+':
|
||||
op = OP_PLUS;
|
||||
break;
|
||||
case '-':
|
||||
op = OP_MINUS;
|
||||
break;
|
||||
case '*':
|
||||
op = OP_MULT;
|
||||
break;
|
||||
case '/':
|
||||
op = OP_DIV;
|
||||
break;
|
||||
case '|':
|
||||
op = OP_OR;
|
||||
break;
|
||||
case '&':
|
||||
op = OP_AND;
|
||||
break;
|
||||
default:
|
||||
goto out;
|
||||
}
|
||||
if (prio >= op_prio_table[op])
|
||||
break;
|
||||
s++;
|
||||
new = malloc(sizeof(struct term));
|
||||
new->op = op;
|
||||
new->left = fn;
|
||||
if (new == NULL)
|
||||
goto out_error;
|
||||
new->right = parse_term(&s, op_prio_table[op]);
|
||||
if (new->right == NULL) {
|
||||
free(new);
|
||||
goto out_error;
|
||||
}
|
||||
fn = new;
|
||||
}
|
||||
out:
|
||||
*p = s;
|
||||
return fn;
|
||||
out_error:
|
||||
if (fn)
|
||||
free_term(fn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static double get_value(struct term *fn)
|
||||
{
|
||||
double value = 0;
|
||||
char *procinfo;
|
||||
unsigned int history_index;
|
||||
|
||||
if (fn->index <= history_current)
|
||||
history_index = history_current - fn->index;
|
||||
else
|
||||
history_index = history_max + 1 - (fn->index - history_current);
|
||||
|
||||
switch (fn->op) {
|
||||
case OP_SYMBOL_MEMINFO:
|
||||
procinfo = meminfo + history_index * meminfo_size;
|
||||
value = get_proc_value(procinfo, fn->proc_name, ':');
|
||||
break;
|
||||
case OP_SYMBOL_VMSTAT:
|
||||
procinfo = vmstat + history_index * vmstat_size;
|
||||
value = get_proc_value(procinfo, fn->proc_name, ' ');
|
||||
break;
|
||||
case OP_SYMBOL_CPUSTAT:
|
||||
procinfo = cpustat + history_index * cpustat_size;
|
||||
value = get_proc_value(procinfo, fn->proc_name, ' ');
|
||||
break;
|
||||
case OP_SYMBOL_TIME:
|
||||
value = timestamps[history_index];
|
||||
break;
|
||||
default:
|
||||
cpuplugd_exit("Invalid term specified: %i\n", fn->op);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double eval_double(struct term *fn, struct symbols *symbols)
|
||||
{
|
||||
double a, b, sum;
|
||||
|
||||
switch (fn->op) {
|
||||
case OP_SYMBOL_LOADAVG:
|
||||
return symbols->loadavg;
|
||||
case OP_SYMBOL_RUNABLE:
|
||||
return symbols->runnable_proc;
|
||||
case OP_SYMBOL_CPUS:
|
||||
return symbols->onumcpus;
|
||||
case OP_SYMBOL_USER:
|
||||
return symbols->user;
|
||||
case OP_SYMBOL_NICE:
|
||||
return symbols->nice;
|
||||
case OP_SYMBOL_SYSTEM:
|
||||
return symbols->system;
|
||||
case OP_SYMBOL_IDLE:
|
||||
return symbols->idle;
|
||||
case OP_SYMBOL_IOWAIT:
|
||||
return symbols->iowait;
|
||||
case OP_SYMBOL_IRQ:
|
||||
return symbols->irq;
|
||||
case OP_SYMBOL_SOFTIRQ:
|
||||
return symbols->softirq;
|
||||
case OP_SYMBOL_STEAL:
|
||||
return symbols->steal;
|
||||
case OP_SYMBOL_GUEST:
|
||||
return symbols->guest;
|
||||
case OP_SYMBOL_GUEST_NICE:
|
||||
return symbols->guest_nice;
|
||||
case OP_SYMBOL_FREEMEM:
|
||||
return symbols->freemem;
|
||||
case OP_SYMBOL_APCR:
|
||||
return symbols->apcr;
|
||||
case OP_SYMBOL_SWAPRATE:
|
||||
return symbols->swaprate;
|
||||
case OP_SYMBOL_MEMINFO:
|
||||
case OP_SYMBOL_VMSTAT:
|
||||
case OP_SYMBOL_CPUSTAT:
|
||||
case OP_SYMBOL_TIME:
|
||||
return get_value(fn);
|
||||
case OP_CONST:
|
||||
return fn->value;
|
||||
case OP_NEG:
|
||||
return -eval_double(fn->left, symbols);
|
||||
case OP_PLUS:
|
||||
return eval_double(fn->left, symbols) +
|
||||
eval_double(fn->right, symbols);
|
||||
case OP_MINUS:
|
||||
return eval_double(fn->left, symbols) -
|
||||
eval_double(fn->right, symbols);
|
||||
case OP_MULT:
|
||||
a = eval_double(fn->left, symbols);
|
||||
b = eval_double(fn->right, symbols);
|
||||
sum = a*b;
|
||||
return sum;
|
||||
/*return eval_double(fn->left, symbols) *
|
||||
eval_double(fn->right, symbols);*/
|
||||
case OP_DIV:
|
||||
a = eval_double(fn->left, symbols);
|
||||
b = eval_double(fn->right, symbols);
|
||||
sum = a/b;
|
||||
return sum;
|
||||
/*return eval_double(fn->left, symbols) /
|
||||
eval_double(fn->right, symbols); */
|
||||
case OP_NOT:
|
||||
case OP_AND:
|
||||
case OP_OR:
|
||||
case OP_GREATER:
|
||||
case OP_LESSER:
|
||||
case VAR_LOAD:
|
||||
case VAR_RUN:
|
||||
case VAR_ONLINE:
|
||||
cpuplugd_exit("Invalid term specified: %i\n", fn->op);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int eval_term(struct term *fn, struct symbols *symbols)
|
||||
{
|
||||
if (fn == NULL || symbols == NULL)
|
||||
return 0.0;
|
||||
switch (fn->op) {
|
||||
case OP_NOT:
|
||||
return !eval_term(fn->left, symbols);
|
||||
case OP_OR:
|
||||
return eval_term(fn->left, symbols) == 1 ||
|
||||
eval_term(fn->right, symbols) == 1;
|
||||
case OP_AND:
|
||||
return eval_term(fn->left, symbols) == 1 &&
|
||||
eval_term(fn->right, symbols) == 1;
|
||||
case OP_GREATER:
|
||||
return eval_double(fn->left, symbols) >
|
||||
eval_double(fn->right, symbols);
|
||||
case OP_LESSER:
|
||||
return eval_double(fn->left, symbols) <
|
||||
eval_double(fn->right, symbols);
|
||||
default:
|
||||
return eval_double(fn, symbols) != 0.0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user