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:
Michael Holzheu
2017-08-07 16:13:17 +02:00
commit b627b8d8e1
647 changed files with 168974 additions and 0 deletions

23
cpuplugd/Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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(&timestamps[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(&timestamps[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
View 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)

View 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
View 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
View 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;
}
}