#include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "cpu" #define LOG_ENABLE_DBG 0 #define SMALLEST_INTERVAL 500 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" struct cpu_stats { uint32_t *prev_cores_idle; uint32_t *prev_cores_nidle; uint32_t *cur_cores_idle; uint32_t *cur_cores_nidle; }; struct private { struct particle *label; uint16_t interval; struct cpu_stats cpu_stats; }; static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free(m->cpu_stats.prev_cores_idle); free(m->cpu_stats.prev_cores_nidle); free(m->cpu_stats.cur_cores_idle); free(m->cpu_stats.cur_cores_nidle); free(m); module_default_destroy(mod); } static const char * description(struct module *mod) { return "cpu"; } static uint32_t get_cpu_nb_cores() { uint32_t nb_cores = 0; FILE *fp = NULL; char *line = NULL; size_t len = 0; ssize_t read; fp = fopen("/proc/cpuinfo", "r"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/cpuinfo"); return 0; } while ((read = getline(&line, &len, fp)) != -1) { if (strncmp(line, "siblings", sizeof("siblings") - 1) == 0) { char *pos = (char *)memchr(line, ':', read); if (pos == NULL) { LOG_ERR("unable to parse siblings field to find the number of cores"); return 0; } errno = 0; nb_cores = strtoul(pos + 1, NULL, 10); if (errno == ERANGE) { LOG_ERR("number of cores is out of range"); } break; } } fclose(fp); free(line); return nb_cores; } static bool parse_proc_stat_line(const char *line, int32_t *core, uint32_t *user, uint32_t *nice, uint32_t *system, uint32_t *idle, uint32_t *iowait, uint32_t *irq, uint32_t *softirq, uint32_t *steal, uint32_t *guest, uint32_t *guestnice) { if (line[sizeof("cpu") - 1] == ' ') { int read = sscanf(line, "cpu %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice); *core = -1; return read == 10; } else { int read = sscanf(line, "cpu%" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32, core, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice); return read == 11; } } static uint8_t get_cpu_usage_percent(const struct cpu_stats *cpu_stats, int8_t core_idx) { uint32_t prev_total = cpu_stats->prev_cores_idle[core_idx + 1] + cpu_stats->prev_cores_nidle[core_idx + 1]; uint32_t cur_total = cpu_stats->cur_cores_idle[core_idx + 1] + cpu_stats->cur_cores_nidle[core_idx + 1]; double totald = cur_total - prev_total; double nidled = cpu_stats->cur_cores_nidle[core_idx + 1] - cpu_stats->prev_cores_nidle[core_idx + 1]; double percent = (nidled * 100) / (totald + 1); return round(percent); } static void refresh_cpu_stats(struct cpu_stats *cpu_stats) { uint32_t nb_cores = get_cpu_nb_cores(); int32_t core = 0; uint32_t user = 0; uint32_t nice = 0; uint32_t system = 0; uint32_t idle = 0; uint32_t iowait = 0; uint32_t irq = 0; uint32_t softirq = 0; uint32_t steal = 0; uint32_t guest = 0; uint32_t guestnice = 0; FILE *fp = NULL; char *line = NULL; size_t len = 0; ssize_t read; fp = fopen("/proc/stat", "r"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/stat"); return; } while ((read = getline(&line, &len, fp)) != -1) { if (strncmp(line, "cpu", sizeof("cpu") - 1) == 0) { if (!parse_proc_stat_line(line, &core, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guestnice) || core < -1 || core >= (int32_t)nb_cores) { LOG_ERR("unable to parse /proc/stat line"); goto exit; } cpu_stats->prev_cores_idle[core + 1] = cpu_stats->cur_cores_idle[core + 1]; cpu_stats->prev_cores_nidle[core + 1] = cpu_stats->cur_cores_nidle[core + 1]; cpu_stats->cur_cores_idle[core + 1] = idle + iowait; cpu_stats->cur_cores_nidle[core + 1] = user + nice + system + irq + softirq + steal; } } exit: fclose(fp); free(line); } static struct exposable * content(struct module *mod) { const struct private *p = mod->private; uint32_t nb_cores = get_cpu_nb_cores(); char cpu_name[32]; struct tag_set tags; tags.count = nb_cores + 1; tags.tags = calloc(tags.count, sizeof(*tags.tags)); mtx_lock(&mod->lock); uint8_t cpu_usage = get_cpu_usage_percent(&p->cpu_stats, -1); tags.tags[0] = tag_new_int_range(mod, "cpu", cpu_usage, 0, 100); for (uint32_t i = 0; i < nb_cores; ++i) { uint8_t cpu_usage = get_cpu_usage_percent(&p->cpu_stats, i); snprintf(cpu_name, sizeof(cpu_name), "cpu%u", i); tags.tags[i + 1] = tag_new_int_range(mod, cpu_name, cpu_usage, 0, 100); } mtx_unlock(&mod->lock); struct exposable *exposable = p->label->instantiate(p->label, &tags); tag_set_destroy(&tags); free(tags.tags); return exposable; } static int run(struct module *mod) { const struct bar *bar = mod->bar; bar->refresh(bar); struct private *p = mod->private; while (true) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval); if (res < 0) { if (EINTR == errno) continue; LOG_ERRNO("unable to poll abort fd"); return -1; } if (fds[0].revents & POLLIN) break; mtx_lock(&mod->lock); refresh_cpu_stats(&p->cpu_stats); mtx_unlock(&mod->lock); bar->refresh(bar); } return 0; } static struct module * cpu_new(uint16_t interval, struct particle *label) { struct private *p = calloc(1, sizeof(*p)); p->label = label; uint32_t nb_cores = get_cpu_nb_cores(); p->interval = interval; p->cpu_stats.prev_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_nidle)); p->cpu_stats.prev_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_idle)); p->cpu_stats.cur_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_nidle)); p->cpu_stats.cur_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_idle)); struct module *mod = module_common_new(); mod->private = p; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *interval = yml_get_value(node, "interval"); const struct yml_node *c = yml_get_value(node, "content"); return cpu_new(interval == NULL ? SMALLEST_INTERVAL : yml_value_as_int(interval), conf_to_particle(c, inherited)); } static bool conf_verify_interval(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_unsigned(chain, node)) return false; if (yml_value_as_int(node) < SMALLEST_INTERVAL) { LOG_ERR("%s: interval value cannot be less than %d ms", conf_err_prefix(chain, node), SMALLEST_INTERVAL); return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"interval", false, &conf_verify_interval}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_cpu_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_cpu_iface"))); #endif