Multiple CPU support for cpu_usage (#209)

This change addresses the issue #199 asking for multiple CPU support. It
takes an arbitrary CPU number and outputs its usage using the same
arithmetics as for CPU aggregation. It currently doesn't support
FreeBSD.
This commit is contained in:
eplanet 2017-03-26 12:54:07 +02:00 committed by Michael Stapelberg
parent 3ae0decbb3
commit 94651257ce
16 changed files with 149 additions and 28 deletions

View File

@ -422,6 +422,7 @@ int main(int argc, char *argv[]) {
CFG_STR("format", "%usage", CFGF_NONE),
CFG_STR("format_above_threshold", NULL, CFGF_NONE),
CFG_STR("format_above_degraded_threshold", NULL, CFGF_NONE),
CFG_STR("path", "/proc/stat", CFGF_NONE),
CFG_FLOAT("max_threshold", 95, CFGF_NONE),
CFG_FLOAT("degraded_threshold", 90, CFGF_NONE),
CFG_CUSTOM_ALIGN_OPT,
@ -751,7 +752,7 @@ int main(int argc, char *argv[]) {
CASE_SEC("cpu_usage") {
SEC_OPEN_MAP("cpu_usage");
print_cpu_usage(json_gen, buffer, cfg_getstr(sec, "format"), cfg_getstr(sec, "format_above_threshold"), cfg_getstr(sec, "format_above_degraded_threshold"), cfg_getfloat(sec, "max_threshold"), cfg_getfloat(sec, "degraded_threshold"));
print_cpu_usage(json_gen, buffer, cfg_getstr(sec, "format"), cfg_getstr(sec, "format_above_threshold"), cfg_getstr(sec, "format_above_degraded_threshold"), cfg_getstr(sec, "path"), cfg_getfloat(sec, "max_threshold"), cfg_getfloat(sec, "degraded_threshold"));
SEC_CLOSE_MAP;
}
}

View File

@ -220,7 +220,7 @@ void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface,
void print_run_watch(yajl_gen json_gen, char *buffer, const char *title, const char *pidfile, const char *format, const char *format_down);
void print_path_exists(yajl_gen json_gen, char *buffer, const char *title, const char *path, const char *format, const char *format_down);
void print_cpu_temperature_info(yajl_gen json_gen, char *buffer, int zone, const char *path, const char *format, const char *format_above_threshold, int);
void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format, const char *format_above_threshold, const char *format_above_degraded_threshold, const float max_threshold, const float degraded_threshold);
void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format, const char *format_above_threshold, const char *format_above_degraded_threshold, const char *path, const float max_threshold, const float degraded_threshold);
void print_eth_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down);
void print_load(yajl_gen json_gen, char *buffer, const char *format, const char *format_above_threshold, const float max_threshold);
void print_volume(yajl_gen json_gen, char *buffer, const char *fmt, const char *fmt_muted, const char *device, const char *mixer, int mixer_idx);

View File

@ -405,13 +405,16 @@ format_above_threshold.
It is possible to define a degraded_threshold that will color the load
value yellow in case the CPU average over the last interval is getting
higher than the configured threshold. Defaults to 90. The output format
higher than the configured threshold. Defaults to 90. The output format
when above degraded threshold can be customized with
format_above_degraded_threshold.
For displaying the Nth CPU usage, you can use the %cpu<N> format string,
starting from %cpu0. This feature is currently not supported in FreeBSD.
*Example order*: +cpu_usage+
*Example format*: +%usage+
*Example format*: +all: %usage CPU_0: %cpu0 CPU_1: %cpu1+
*Example max_threshold*: +75+

View File

@ -33,36 +33,68 @@
#include "i3status.h"
static int prev_total = 0;
static int prev_idle = 0;
struct cpu_usage {
int user;
int nice;
int system;
int idle;
int total;
};
static int cpu_count = 0;
static struct cpu_usage prev_all = {0, 0, 0, 0, 0};
static struct cpu_usage *prev_cpus = NULL;
static struct cpu_usage *curr_cpus = NULL;
/*
* Reads the CPU utilization from /proc/stat and returns the usage as a
* percentage.
*
*/
void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format, const char *format_above_threshold, const char *format_above_degraded_threshold, const float max_threshold, const float degraded_threshold) {
void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format, const char *format_above_threshold, const char *format_above_degraded_threshold, const char *path, const float max_threshold, const float degraded_threshold) {
const char *selected_format = format;
const char *walk;
char *outwalk = buffer;
int curr_user = 0, curr_nice = 0, curr_system = 0, curr_idle = 0, curr_total;
struct cpu_usage curr_all = {0, 0, 0, 0, 0};
int diff_idle, diff_total, diff_usage;
bool colorful_output = false;
#if defined(LINUX)
static char statpath[512];
char buf[1024];
strcpy(statpath, "/proc/stat");
if (!slurp(statpath, buf, sizeof(buf)) ||
sscanf(buf, "cpu %d %d %d %d", &curr_user, &curr_nice, &curr_system, &curr_idle) != 4)
goto error;
curr_total = curr_user + curr_nice + curr_system + curr_idle;
diff_idle = curr_idle - prev_idle;
diff_total = curr_total - prev_total;
// Detecting if CPU count has changed
int curr_cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
if (curr_cpu_count != cpu_count) {
cpu_count = curr_cpu_count;
free(prev_cpus);
prev_cpus = (struct cpu_usage *)calloc(cpu_count, sizeof(struct cpu_usage));
free(curr_cpus);
curr_cpus = (struct cpu_usage *)calloc(cpu_count, sizeof(struct cpu_usage));
}
char buf[4096];
if (!slurp(path, buf, sizeof(buf)))
goto error;
// Parsing all cpu values using strtok
if (strtok(buf, "\n") == NULL)
goto error;
char *buf_itr = NULL;
for (int cpu_idx = 0; cpu_idx < cpu_count; cpu_idx++) {
buf_itr = strtok(NULL, "\n");
int curr_cpu_idx = -1;
if (!buf_itr || sscanf(buf_itr, "cpu%d %d %d %d %d", &curr_cpu_idx, &curr_cpus[cpu_idx].user, &curr_cpus[cpu_idx].nice, &curr_cpus[cpu_idx].system, &curr_cpus[cpu_idx].idle) != 5 || curr_cpu_idx != cpu_idx)
goto error;
curr_cpus[cpu_idx].total = curr_cpus[cpu_idx].user + curr_cpus[cpu_idx].nice + curr_cpus[cpu_idx].system + curr_cpus[cpu_idx].idle;
curr_all.user += curr_cpus[cpu_idx].user;
curr_all.nice += curr_cpus[cpu_idx].nice;
curr_all.system += curr_cpus[cpu_idx].system;
curr_all.idle += curr_cpus[cpu_idx].idle;
curr_all.total += curr_cpus[cpu_idx].total;
}
diff_idle = curr_all.idle - prev_all.idle;
diff_total = curr_all.total - prev_all.total;
diff_usage = (diff_total ? (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 : 0);
prev_total = curr_total;
prev_idle = curr_idle;
prev_all = curr_all;
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__)
@ -84,16 +116,15 @@ void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format, const
goto error;
#endif
curr_user = cp_time[CP_USER];
curr_nice = cp_time[CP_NICE];
curr_system = cp_time[CP_SYS];
curr_idle = cp_time[CP_IDLE];
curr_total = curr_user + curr_nice + curr_system + curr_idle;
diff_idle = curr_idle - prev_idle;
diff_total = curr_total - prev_total;
curr_all.user = cp_time[CP_USER];
curr_all.nice = cp_time[CP_NICE];
curr_all.system = cp_time[CP_SYS];
curr_all.idle = cp_time[CP_IDLE];
curr_all.total = curr_all.user + curr_all.nice + curr_all.system + curr_all.idle;
diff_idle = curr_all.idle - prev_all.idle;
diff_total = curr_all.total - prev_all.total;
diff_usage = (diff_total ? (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 : 0);
prev_total = curr_total;
prev_idle = curr_idle;
prev_all = curr_all;
#else
goto error;
#endif
@ -120,8 +151,32 @@ void print_cpu_usage(yajl_gen json_gen, char *buffer, const char *format, const
outwalk += sprintf(outwalk, "%02d%s", diff_usage, pct_mark);
walk += strlen("usage");
}
#if defined(LINUX)
if (BEGINS_WITH(walk + 1, "cpu")) {
int number = 0;
sscanf(walk + 1, "cpu%d", &number);
if (number < 0 || number >= cpu_count) {
fprintf(stderr, "provided CPU number '%d' above detected number of CPU %d\n", number, cpu_count);
} else {
int cpu_diff_idle = curr_cpus[number].idle - prev_cpus[number].idle;
int cpu_diff_total = curr_cpus[number].total - prev_cpus[number].total;
int cpu_diff_usage = (cpu_diff_total ? (1000 * (cpu_diff_total - cpu_diff_idle) / cpu_diff_total + 5) / 10 : 0);
outwalk += sprintf(outwalk, "%02d%s", cpu_diff_usage, pct_mark);
}
int padding = 1;
int step = 10;
while (step < number) {
step *= 10;
padding++;
}
walk += strlen("cpu") + padding;
}
#endif
}
for (int i = 0; i < cpu_count; i++)
prev_cpus[i] = curr_cpus[i];
if (colorful_output)
END_COLOR;

View File

@ -0,0 +1 @@
all: 75% CPU_0: 100% CPU_1: 50%

View File

@ -0,0 +1,12 @@
general {
output_format = "none"
}
order += "cpu_usage"
cpu_usage {
format = "all: %usage CPU_0: %cpu0 CPU_1: %cpu1"
path = "testcases/010-cpu-usage/stat"
max_threshold = 90
degraded_threshold = 75
}

View File

@ -0,0 +1,3 @@
cpu 0 0 0 0 0 0 0 0 0 0
cpu0 100 0 0 0 0 0 0 0 0 0
cpu1 50 0 0 50 0 0 0 0 0 0

View File

@ -0,0 +1 @@
all: 50% CPU_0: 00% CPU_1: 100%

View File

@ -0,0 +1,12 @@
general {
output_format = "none"
}
order += "cpu_usage"
cpu_usage {
format = "all: %usage CPU_0: %cpu0 CPU_1: %cpu1"
path = "testcases/011-cpu-usage/stat"
max_threshold = 90
degraded_threshold = 75
}

View File

@ -0,0 +1,3 @@
cpu 0 0 0 0 0 0 0 0 0 0
cpu0 0 0 0 300 0 0 0 0 0 0
cpu1 100 100 100 0 0 0 0 0 0 0

View File

@ -0,0 +1 @@
cant read cpu usage

View File

@ -0,0 +1,12 @@
general {
output_format = "none"
}
order += "cpu_usage"
cpu_usage {
format = "all: %usage CPU_0: %cpu0 CPU_1: %cpu1"
path = "testcases/012-cpu-usage-error/stat"
max_threshold = 90
degraded_threshold = 75
}

View File

@ -0,0 +1,2 @@
cpu 0 0 0 0 0 0 0 0 0 0
cpu0 100 0 0 0 0 0 0 0 0 0

View File

@ -0,0 +1 @@
cant read cpu usage

View File

@ -0,0 +1,12 @@
general {
output_format = "none"
}
order += "cpu_usage"
cpu_usage {
format = "all: %usage CPU_0: %cpu0 CPU_1: %cpu1"
path = "testcases/013-cpu-usage-error/stat"
max_threshold = 90
degraded_threshold = 75
}

View File

@ -0,0 +1,2 @@
cpu0 100 0 0 0 0 0 0 0 0 0
cpu1 50 0 0 50 0 0 0 0 0 0