mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-19 19:25:41 +02:00
modules: add cpu module
This commit is contained in:
parent
337ce7681f
commit
ae5c7e0fc3
6 changed files with 341 additions and 0 deletions
|
@ -17,6 +17,7 @@
|
|||
* 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
|
||||
|
|
|
@ -13,6 +13,7 @@ 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('.')
|
||||
|
|
42
doc/yambar-modules-cpu.5.scd
Normal file
42
doc/yambar-modules-cpu.5.scd
Normal 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)
|
294
modules/cpu.c
Normal file
294
modules/cpu.c
Normal 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
|
|
@ -17,6 +17,7 @@ mod_data = {
|
|||
'backlight': [[], [m, udev]],
|
||||
'battery': [[], [udev]],
|
||||
'clock': [[], []],
|
||||
'cpu': [[], []],
|
||||
'mem': [[], []],
|
||||
'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
||||
'label': [[], []],
|
||||
|
|
2
plugin.c
2
plugin.c
|
@ -49,6 +49,7 @@ EXTERN_MODULE(sway_xkb);
|
|||
EXTERN_MODULE(script);
|
||||
EXTERN_MODULE(xkb);
|
||||
EXTERN_MODULE(xwindow);
|
||||
EXTERN_MODULE(cpu);
|
||||
EXTERN_MODULE(mem);
|
||||
|
||||
EXTERN_PARTICLE(empty);
|
||||
|
@ -138,6 +139,7 @@ init(void)
|
|||
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);
|
||||
|
|
Loading…
Add table
Reference in a new issue