diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..528a36b --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +--- +BasedOnStyle: GNU +IndentWidth: 4 +--- +Language: Cpp +PointerAlignment: Right +ColumnLimit: 120 +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: false + AfterClass: false + SplitEmptyFunction: true + AfterFunction: true + AfterStruct: false + +SpaceBeforeParens: ControlStatements +Cpp11BracedListStyle: true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ef74858 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space +indent_size = 4 +max_line_length = 70 + +[{meson.build,PKGBUILD}] +indent_size = 2 + +[*.scd] +indent_style = tab +trim_trailing_whitespace = false diff --git a/CHANGELOG.md b/CHANGELOG.md index fba854f..9a834b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ * border: new decoration. * i3/sway: new boolean tag: `empty` (https://codeberg.org/dnkl/yambar/issues/139). +* mem: a module handling system memory monitoring +* cpu: a module offering cpu usage monitoring ### Changed diff --git a/doc/meson.build b/doc/meson.build index a1550c4..0d7f873 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -13,6 +13,8 @@ foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd', 'yambar-modules-script.5.scd', 'yambar-modules-sway-xkb.5.scd', 'yambar-modules-sway.5.scd', 'yambar-modules-xkb.5.scd', 'yambar-modules-xwindow.5.scd', 'yambar-modules.5.scd', + 'yambar-modules-cpu.5.scd', + 'yambar-modules-mem.5.scd', 'yambar-particles.5.scd', 'yambar-tags.5.scd'] parts = man_src.split('.') name = parts[-3] diff --git a/doc/yambar-modules-cpu.5.scd b/doc/yambar-modules-cpu.5.scd new file mode 100644 index 0000000..2a05fdf --- /dev/null +++ b/doc/yambar-modules-cpu.5.scd @@ -0,0 +1,42 @@ +yambar-modules-cpu(5) + +# NAME +cpu - This module provides the CPU usage + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| cpu +: range +: Current usage of the whole CPU in percent +| cpu<0..X> +: range +: Current usage of CPU core X in percent + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| interval +: int +: no +: Refresh interval of the CPU usage stats in ms (default=500). Cannot be less then 500 ms + +# EXAMPLES + +``` +bar: + left: + - cpu: + interval: 2500 + content: + string: {text: "{cpu1}%"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) diff --git a/doc/yambar-modules-mem.5.scd b/doc/yambar-modules-mem.5.scd new file mode 100644 index 0000000..cec575c --- /dev/null +++ b/doc/yambar-modules-mem.5.scd @@ -0,0 +1,51 @@ +yambar-modules-mem(5) + +# NAME +mem - This module provides the memory usage + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| free +: int +: Free memory in bytes +| used +: int +: Used memory in bytes +| total +: int +: Total memory in bytes +| percent_free +: range +: Free memory in percent +| percent_used +: range +: Used memory in percent + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| interval +: string +: no +: Refresh interval of the memory usage stats in ms (default=500). Cannot be less then 500 ms + +# EXAMPLES + +``` +bar: + left: + - mem: + interval: 2500 + content: + string: {text: "{used:mb}MB"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) diff --git a/modules/cpu.c b/modules/cpu.c new file mode 100644 index 0000000..0967dfd --- /dev/null +++ b/modules/cpu.c @@ -0,0 +1,294 @@ +#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 diff --git a/modules/mem.c b/modules/mem.c new file mode 100644 index 0000000..d79861e --- /dev/null +++ b/modules/mem.c @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE "mem" +#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 private +{ + struct particle *label; + uint16_t interval; +}; + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + m->label->destroy(m->label); + free(m); + module_default_destroy(mod); +} + +static const char * +description(struct module *mod) +{ + return "mem"; +} + +static bool +get_mem_stats(uint64_t *mem_free, uint64_t *mem_total) +{ + bool mem_total_found = false; + bool mem_free_found = false; + + FILE *fp = NULL; + char *line = NULL; + size_t len = 0; + ssize_t read = 0; + + fp = fopen("/proc/meminfo", "r"); + if (NULL == fp) { + LOG_ERRNO("unable to open /proc/meminfo"); + return false; + } + + while ((read = getline(&line, &len, fp)) != -1) { + if (strncmp(line, "MemTotal:", sizeof("MemTotal:") - 1) == 0) { + read = sscanf(line + sizeof("MemTotal:") - 1, "%" SCNu64, mem_total); + mem_total_found = (read == 1); + } + if (strncmp(line, "MemAvailable:", sizeof("MemAvailable:") - 1) == 0) { + read = sscanf(line + sizeof("MemAvailable:"), "%" SCNu64, mem_free); + mem_free_found = (read == 1); + } + } + free(line); + + fclose(fp); + + return mem_free_found && mem_total_found; +} + +static struct exposable * +content(struct module *mod) +{ + const struct private *p = mod->private; + uint64_t mem_free = 0; + uint64_t mem_used = 0; + uint64_t mem_total = 0; + + if (!get_mem_stats(&mem_free, &mem_total)) { + LOG_ERR("unable to retrieve the memory stats"); + } + + mem_used = mem_total - mem_free; + + double percent_used = ((double)mem_used * 100) / (mem_total + 1); + double percent_free = ((double)mem_free * 100) / (mem_total + 1); + + struct tag_set tags = { + .tags = (struct tag *[]){tag_new_int(mod, "free", mem_free * 1024), tag_new_int(mod, "used", mem_used * 1024), + tag_new_int(mod, "total", mem_total * 1024), + tag_new_int_range(mod, "percent_free", round(percent_free), 0, 100), + tag_new_int_range(mod, "percent_used", round(percent_used), 0, 100)}, + .count = 5, + }; + + struct exposable *exposable = p->label->instantiate(p->label, &tags); + tag_set_destroy(&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, 1, p->interval); + if (res < 0) { + if (EINTR == errno) { + continue; + } + + LOG_ERRNO("unable to poll abort fd"); + return -1; + } + + if (fds[0].revents & POLLIN) + break; + + bar->refresh(bar); + } + + return 0; +} + +static struct module * +mem_new(uint16_t interval, struct particle *label) +{ + struct private *p = calloc(1, sizeof(*p)); + p->label = label; + p->interval = interval; + + 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 mem_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_mem_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_mem_iface"))); +#endif diff --git a/modules/meson.build b/modules/meson.build index 6b64958..192b094 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -17,6 +17,8 @@ mod_data = { 'backlight': [[], [m, udev]], 'battery': [[], [udev]], 'clock': [[], []], + 'cpu': [[], []], + 'mem': [[], []], 'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]], 'label': [[], []], 'network': [[], []], diff --git a/plugin.c b/plugin.c index 5f31b2e..1faae0e 100644 --- a/plugin.c +++ b/plugin.c @@ -49,6 +49,8 @@ EXTERN_MODULE(sway_xkb); EXTERN_MODULE(script); EXTERN_MODULE(xkb); EXTERN_MODULE(xwindow); +EXTERN_MODULE(cpu); +EXTERN_MODULE(mem); EXTERN_PARTICLE(empty); EXTERN_PARTICLE(list); @@ -136,6 +138,8 @@ init(void) #if defined(HAVE_PLUGIN_xwindow) REGISTER_CORE_MODULE(xwindow, xwindow); #endif + REGISTER_CORE_MODULE(mem, mem); + REGISTER_CORE_MODULE(cpu, cpu); REGISTER_CORE_PARTICLE(empty, empty); REGISTER_CORE_PARTICLE(list, list);