Merge branch 'mem-and-cpu-modules'

This commit is contained in:
Daniel Eklöf 2021-12-21 19:21:44 +01:00
commit 4ff1c43669
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
10 changed files with 623 additions and 0 deletions

17
.clang-format Normal file
View file

@ -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

17
.editorconfig Normal file
View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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)

View file

@ -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)

294
modules/cpu.c Normal file
View file

@ -0,0 +1,294 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#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

192
modules/mem.c Normal file
View file

@ -0,0 +1,192 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#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

View file

@ -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': [[], []],

View file

@ -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);