diff options
Diffstat (limited to 'src/collectors/proc.plugin/sys_class_power_supply.c')
-rw-r--r-- | src/collectors/proc.plugin/sys_class_power_supply.c | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/src/collectors/proc.plugin/sys_class_power_supply.c b/src/collectors/proc.plugin/sys_class_power_supply.c new file mode 100644 index 000000000..c6be72679 --- /dev/null +++ b/src/collectors/proc.plugin/sys_class_power_supply.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "plugin_proc.h" + +#define PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME "/sys/class/power_supply" +#define PROP_VALUE_LENGTH_MAX 30 + +const char *ps_property_names[] = { "charge", "energy", "voltage"}; +const char *ps_property_titles[] = {"Battery charge", "Battery energy", "Power supply voltage"}; +const char *ps_property_units[] = { "Ah", "Wh", "V"}; + +const long ps_property_priorities[] = { + NETDATA_CHART_PRIO_POWER_SUPPLY_CHARGE, + NETDATA_CHART_PRIO_POWER_SUPPLY_ENERGY, + NETDATA_CHART_PRIO_POWER_SUPPLY_VOLTAGE +}; + + +const char *ps_property_dim_names[] = {"empty_design", "empty", "now", "full", "full_design", + "empty_design", "empty", "now", "full", "full_design", + "min_design", "min", "now", "max", "max_design"}; + +struct ps_property_dim { + char *name; + char *filename; + int fd; + + RRDDIM *rd; + unsigned long long value; + int always_zero; + + struct ps_property_dim *next; +}; + +struct ps_property { + char *name; + char *title; + char *units; + + long priority; + + RRDSET *st; + + struct ps_property_dim *property_dim_root; + + struct ps_property *next; +}; + +struct simple_property { + char *filename; + int fd; + + RRDSET *st; + RRDDIM *rd; + bool ok; + unsigned long long value; +}; + +struct power_supply { + char *name; + uint32_t hash; + int found; + + struct simple_property *capacity, *power; + + struct ps_property *property_root; + + struct power_supply *next; +}; + +static struct power_supply *power_supply_root = NULL; +static int files_num = 0; + +static void free_simple_prop(struct simple_property *prop) { + if(likely(prop)) { + if(likely(prop->st)) rrdset_is_obsolete___safe_from_collector_thread(prop->st); + freez(prop->filename); + if(likely(prop->fd != -1)) close(prop->fd); + files_num--; + freez(prop); + } +} + +void power_supply_free(struct power_supply *ps) { + if(likely(ps)) { + + // free capacity structure + free_simple_prop(ps->capacity); + free_simple_prop(ps->power); + freez(ps->name); + + struct ps_property *pr = ps->property_root; + while(likely(pr)) { + + // free dimensions + struct ps_property_dim *pd = pr->property_dim_root; + while(likely(pd)) { + freez(pd->name); + freez(pd->filename); + if(likely(pd->fd != -1)) close(pd->fd); + files_num--; + struct ps_property_dim *d = pd; + pd = pd->next; + freez(d); + } + + // free properties + if(likely(pr->st)) rrdset_is_obsolete___safe_from_collector_thread(pr->st); + freez(pr->name); + freez(pr->title); + freez(pr->units); + struct ps_property *p = pr; + pr = pr->next; + freez(p); + } + + // remove power supply from linked list + if(likely(ps == power_supply_root)) { + power_supply_root = ps->next; + } + else { + struct power_supply *last; + for(last = power_supply_root; last && last->next != ps; last = last->next); + if(likely(last)) last->next = ps->next; + } + + freez(ps); + } +} + +static void add_labels_to_power_supply(struct power_supply *ps, RRDSET *st) { + rrdlabels_add(st->rrdlabels, "device", ps->name, RRDLABEL_SRC_AUTO); +} + +static void read_simple_property(struct simple_property *prop, bool keep_fds_open) { + char buffer[PROP_VALUE_LENGTH_MAX + 1]; + + prop->ok = false; + if(unlikely(prop->fd == -1)) { + prop->fd = open(prop->filename, O_RDONLY | O_CLOEXEC, 0666); + if(unlikely(prop->fd == -1)) { + collector_error("Cannot open file '%s'", prop->filename); + return; + } + } + ssize_t r = read(prop->fd, buffer, PROP_VALUE_LENGTH_MAX); + if(unlikely(r < 1)) { + collector_error("Cannot read file '%s'", prop->filename); + } + else { + buffer[r] = '\0'; + prop->value = str2ull(buffer, NULL); + prop->ok = true; + } + + if(unlikely(!keep_fds_open)) { + close(prop->fd); + prop->fd = -1; + } + else if(unlikely(prop->ok && lseek(prop->fd, 0, SEEK_SET) == -1)) { + collector_error("Cannot seek in file '%s'", prop->filename); + close(prop->fd); + prop->fd = -1; + } + return; +} + +static void rrdset_create_simple_prop(struct power_supply *ps, struct simple_property *prop, char *title, char *dim, collected_number divisor, char *units, long priority, int update_every) { + if(unlikely(!prop->st)) { + char id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "powersupply_%s", dim); + snprintfz(context, RRD_ID_LENGTH_MAX, "powersupply.%s", dim); + + prop->st = rrdset_create_localhost( + id + , ps->name + , NULL + , dim + , context + , title + , units + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME + , priority + , update_every + , RRDSET_TYPE_LINE + ); + + add_labels_to_power_supply(ps, prop->st); + } + + if(unlikely(!prop->rd)) prop->rd = rrddim_add(prop->st, dim, NULL, 1, divisor, RRD_ALGORITHM_ABSOLUTE); + rrddim_set_by_pointer(prop->st, prop->rd, prop->value); + + rrdset_done(prop->st); +} + +int do_sys_class_power_supply(int update_every, usec_t dt) { + (void)dt; + static int do_capacity = -1, do_power = -1, do_property[3] = {-1}; + static int keep_fds_open = CONFIG_BOOLEAN_NO, keep_fds_open_config = -1; + static char *dirname = NULL; + + if(unlikely(do_capacity == -1)) { + do_capacity = config_get_boolean("plugin:proc:/sys/class/power_supply", "battery capacity", CONFIG_BOOLEAN_YES); + do_power = config_get_boolean("plugin:proc:/sys/class/power_supply", "battery power", CONFIG_BOOLEAN_YES); + do_property[0] = config_get_boolean("plugin:proc:/sys/class/power_supply", "battery charge", CONFIG_BOOLEAN_NO); + do_property[1] = config_get_boolean("plugin:proc:/sys/class/power_supply", "battery energy", CONFIG_BOOLEAN_NO); + do_property[2] = config_get_boolean("plugin:proc:/sys/class/power_supply", "power supply voltage", CONFIG_BOOLEAN_NO); + + keep_fds_open_config = config_get_boolean_ondemand("plugin:proc:/sys/class/power_supply", "keep files open", CONFIG_BOOLEAN_AUTO); + + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/class/power_supply"); + dirname = config_get("plugin:proc:/sys/class/power_supply", "directory to monitor", filename); + } + + DIR *dir = opendir(dirname); + if(unlikely(!dir)) { + collector_error("Cannot read directory '%s'", dirname); + return 1; + } + + struct dirent *de = NULL; + while(likely(de = readdir(dir))) { + if(likely(de->d_type == DT_DIR + && ( + (de->d_name[0] == '.' && de->d_name[1] == '\0') + || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') + ))) + continue; + + if(likely(de->d_type == DT_LNK || de->d_type == DT_DIR)) { + uint32_t hash = simple_hash(de->d_name); + + struct power_supply *ps; + for(ps = power_supply_root; ps; ps = ps->next) { + if(unlikely(ps->hash == hash && !strcmp(ps->name, de->d_name))) { + ps->found = 1; + break; + } + } + + // allocate memory for power supply and initialize it + if(unlikely(!ps)) { + ps = callocz(sizeof(struct power_supply), 1); + ps->name = strdupz(de->d_name); + ps->hash = simple_hash(de->d_name); + ps->found = 1; + ps->next = power_supply_root; + power_supply_root = ps; + + struct stat stbuf; + if(likely(do_capacity != CONFIG_BOOLEAN_NO)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/%s/%s", dirname, de->d_name, "capacity"); + if (stat(filename, &stbuf) == 0) { + ps->capacity = callocz(sizeof(struct simple_property), 1); + ps->capacity->filename = strdupz(filename); + ps->capacity->fd = -1; + files_num++; + } + } + + if(likely(do_power != CONFIG_BOOLEAN_NO)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/%s/%s", dirname, de->d_name, "power_now"); + if (stat(filename, &stbuf) == 0) { + ps->power = callocz(sizeof(struct simple_property), 1); + ps->power->filename = strdupz(filename); + ps->power->fd = -1; + files_num++; + } + } + + // allocate memory and initialize structures for every property and file found + size_t pr_idx, pd_idx; + size_t prev_idx = 3; // there is no property with this index + + for(pr_idx = 0; pr_idx < 3; pr_idx++) { + if(unlikely(do_property[pr_idx] != CONFIG_BOOLEAN_NO)) { + struct ps_property *pr = NULL; + int min_value_found = 0, max_value_found = 0; + + for(pd_idx = pr_idx * 5; pd_idx < pr_idx * 5 + 5; pd_idx++) { + + // check if file exists + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/%s/%s_%s", dirname, de->d_name, + ps_property_names[pr_idx], ps_property_dim_names[pd_idx]); + if (stat(filename, &stbuf) == 0) { + + if(unlikely(pd_idx == pr_idx * 5 + 1)) + min_value_found = 1; + if(unlikely(pd_idx == pr_idx * 5 + 3)) + max_value_found = 1; + + // add chart + if(unlikely(prev_idx != pr_idx)) { + pr = callocz(sizeof(struct ps_property), 1); + pr->name = strdupz(ps_property_names[pr_idx]); + pr->title = strdupz(ps_property_titles[pr_idx]); + pr->units = strdupz(ps_property_units[pr_idx]); + pr->priority = ps_property_priorities[pr_idx]; + prev_idx = pr_idx; + pr->next = ps->property_root; + ps->property_root = pr; + } + + // add dimension + struct ps_property_dim *pd; + pd= callocz(sizeof(struct ps_property_dim), 1); + pd->name = strdupz(ps_property_dim_names[pd_idx]); + pd->filename = strdupz(filename); + pd->fd = -1; + files_num++; + pd->next = pr->property_dim_root; + pr->property_dim_root = pd; + } + } + + // create a zero empty/min dimension + if(unlikely(max_value_found && !min_value_found)) { + struct ps_property_dim *pd; + pd= callocz(sizeof(struct ps_property_dim), 1); + pd->name = strdupz(ps_property_dim_names[pr_idx * 5 + 1]); + pd->always_zero = 1; + pd->next = pr->property_dim_root; + pr->property_dim_root = pd; + } + } + } + } + + // read capacity file + if(likely(ps->capacity)) { + read_simple_property(ps->capacity, keep_fds_open); + } + + // read power file + if(likely(ps->power)) { + read_simple_property(ps->power, keep_fds_open); + } + + if(unlikely((!ps->power || !ps->power->ok) && (!ps->capacity || !ps->capacity->ok))) { + power_supply_free(ps); + ps = NULL; + } + + // read property files + int read_error = 0; + struct ps_property *pr; + if (likely(ps)) + { + for(pr = ps->property_root; pr && !read_error; pr = pr->next) { + struct ps_property_dim *pd; + for(pd = pr->property_dim_root; pd; pd = pd->next) { + if(likely(!pd->always_zero)) { + char buffer[PROP_VALUE_LENGTH_MAX + 1]; + + if(unlikely(pd->fd == -1)) { + pd->fd = open(pd->filename, O_RDONLY | O_CLOEXEC, 0666); + if(unlikely(pd->fd == -1)) { + collector_error("Cannot open file '%s'", pd->filename); + read_error = 1; + power_supply_free(ps); + break; + } + } + + ssize_t r = read(pd->fd, buffer, PROP_VALUE_LENGTH_MAX); + if(unlikely(r < 1)) { + collector_error("Cannot read file '%s'", pd->filename); + read_error = 1; + power_supply_free(ps); + break; + } + buffer[r] = '\0'; + pd->value = str2ull(buffer, NULL); + + if(unlikely(!keep_fds_open)) { + close(pd->fd); + pd->fd = -1; + } + else if(unlikely(lseek(pd->fd, 0, SEEK_SET) == -1)) { + collector_error("Cannot seek in file '%s'", pd->filename); + close(pd->fd); + pd->fd = -1; + } + } + } + } + } + } + } + + closedir(dir); + + keep_fds_open = keep_fds_open_config; + if(likely(keep_fds_open_config == CONFIG_BOOLEAN_AUTO)) { + if(unlikely(files_num > 32)) + keep_fds_open = CONFIG_BOOLEAN_NO; + else + keep_fds_open = CONFIG_BOOLEAN_YES; + } + + // -------------------------------------------------------------------- + + struct power_supply *ps = power_supply_root; + while(unlikely(ps)) { + if(unlikely(!ps->found)) { + struct power_supply *f = ps; + ps = ps->next; + power_supply_free(f); + continue; + } + + if(likely(ps->capacity && ps->capacity->ok)) { + rrdset_create_simple_prop(ps, ps->capacity, "Battery capacity", "capacity", 1, "percentage", NETDATA_CHART_PRIO_POWER_SUPPLY_CAPACITY, update_every); + } + + if(likely(ps->power && ps->power->ok)) { + rrdset_create_simple_prop(ps, ps->power, "Battery power", "power", 1000000, "W", NETDATA_CHART_PRIO_POWER_SUPPLY_POWER, update_every); + } + + struct ps_property *pr; + for(pr = ps->property_root; pr; pr = pr->next) { + if(unlikely(!pr->st)) { + char id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "powersupply_%s", pr->name); + snprintfz(context, RRD_ID_LENGTH_MAX, "powersupply.%s", pr->name); + + pr->st = rrdset_create_localhost( + id + , ps->name + , NULL + , pr->name + , context + , pr->title + , pr->units + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME + , pr->priority + , update_every + , RRDSET_TYPE_LINE + ); + + add_labels_to_power_supply(ps, pr->st); + } + + struct ps_property_dim *pd; + for(pd = pr->property_dim_root; pd; pd = pd->next) { + if(unlikely(!pd->rd)) pd->rd = rrddim_add(pr->st, pd->name, NULL, 1, 1000000, RRD_ALGORITHM_ABSOLUTE); + rrddim_set_by_pointer(pr->st, pd->rd, pd->value); + } + + rrdset_done(pr->st); + } + + ps->found = 0; + ps = ps->next; + } + + return 0; +} |