| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "plugin_proc.h" |
| 4 | |
| 5 | #define PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME "/sys/class/power_supply" |
| 6 | #define PROP_VALUE_LENGTH_MAX 30 |
| 7 | |
| 8 | #define _COMMON_PLUGIN_NAME PLUGIN_PROC_NAME |
| 9 | #define _COMMON_PLUGIN_MODULE_NAME PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME |
| 10 | #include "../common-contexts/common-contexts.h" |
| 11 | |
| 12 | const char *ps_property_names[] = { "charge", "energy", "voltage"}; |
| 13 | const char *ps_property_titles[] = {"Battery charge", "Battery energy", "Power supply voltage"}; |
| 14 | const char *ps_property_units[] = { "Ah", "Wh", "V"}; |
| 15 | |
| 16 | const long ps_property_priorities[] = { |
| 17 | NETDATA_CHART_PRIO_POWER_SUPPLY_CHARGE, |
| 18 | NETDATA_CHART_PRIO_POWER_SUPPLY_ENERGY, |
| 19 | NETDATA_CHART_PRIO_POWER_SUPPLY_VOLTAGE |
| 20 | }; |
| 21 | |
| 22 | |
| 23 | const char *ps_property_dim_names[] = {"empty_design", "empty", "now", "full", "full_design", |
| 24 | "empty_design", "empty", "now", "full", "full_design", |
| 25 | "min_design", "min", "now", "max", "max_design"}; |
| 26 | |
| 27 | static struct power_supply *power_supply_root = NULL; |
| 28 | static int files_num = 0; |
| 29 | |
| 30 | static void free_simple_prop(struct simple_property *prop) { |
| 31 | if(likely(prop)) { |
| 32 | if(likely(prop->st)) rrdset_is_obsolete___safe_from_collector_thread(prop->st); |
| 33 | freez(prop->filename); |
| 34 | if(likely(prop->fd != -1)) close(prop->fd); |
| 35 | files_num--; |
| 36 | freez(prop); |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | void power_supply_free(struct power_supply *ps) { |
| 41 | if(likely(ps)) { |
| 42 | |
| 43 | // free capacity structure |
| 44 | free_simple_prop(ps->capacity); |
| 45 | free_simple_prop(ps->power); |
| 46 | freez(ps->name); |
| 47 | |
| 48 | struct ps_property *pr = ps->property_root; |
| 49 | while(likely(pr)) { |
| 50 | |
| 51 | // free dimensions |
| 52 | struct ps_property_dim *pd = pr->property_dim_root; |
| 53 | while(likely(pd)) { |
| 54 | freez(pd->name); |
| 55 | freez(pd->filename); |
| 56 | if(likely(pd->fd != -1)) close(pd->fd); |
| 57 | files_num--; |
| 58 | struct ps_property_dim *d = pd; |
| 59 | pd = pd->next; |
| 60 | freez(d); |
| 61 | } |
| 62 | |
| 63 | // free properties |
| 64 | if(likely(pr->st)) rrdset_is_obsolete___safe_from_collector_thread(pr->st); |
| 65 | freez(pr->name); |
| 66 | freez(pr->title); |
| 67 | freez(pr->units); |
| 68 | struct ps_property *p = pr; |
| 69 | pr = pr->next; |
| 70 | freez(p); |
| 71 | } |
| 72 | |
| 73 | // remove power supply from linked list |
| 74 | if(likely(ps == power_supply_root)) { |
| 75 | power_supply_root = ps->next; |
| 76 | } |
| 77 | else { |
| 78 | struct power_supply *last; |
| 79 | for(last = power_supply_root; last && last->next != ps; last = last->next); |
| 80 | if(likely(last)) last->next = ps->next; |
| 81 | } |
| 82 | |
| 83 | freez(ps); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | static void read_simple_property(struct simple_property *prop, bool keep_fds_open) { |
| 88 | char buffer[PROP_VALUE_LENGTH_MAX + 1]; |
| 89 | |
| 90 | prop->ok = false; |
| 91 | if(unlikely(prop->fd == -1)) { |
| 92 | prop->fd = open(prop->filename, O_RDONLY | O_CLOEXEC, 0666); |
| 93 | if(unlikely(prop->fd == -1)) { |
| 94 | collector_error("Cannot open file '%s'", prop->filename); |
| 95 | return; |
| 96 | } |
| 97 | } |
| 98 | ssize_t r = read(prop->fd, buffer, PROP_VALUE_LENGTH_MAX); |
| 99 | if(unlikely(r < 1)) { |
| 100 | collector_error("Cannot read file '%s'", prop->filename); |
| 101 | } |
| 102 | else { |
| 103 | buffer[r] = '\0'; |
| 104 | prop->value = str2ull(buffer, NULL); |
| 105 | prop->ok = true; |
| 106 | } |
| 107 | |
| 108 | if(unlikely(!keep_fds_open)) { |
| 109 | close(prop->fd); |
| 110 | prop->fd = -1; |
| 111 | } |
| 112 | else if(unlikely(prop->ok && lseek(prop->fd, 0, SEEK_SET) == -1)) { |
| 113 | collector_error("Cannot seek in file '%s'", prop->filename); |
| 114 | close(prop->fd); |
| 115 | prop->fd = -1; |
| 116 | } |
| 117 | return; |
| 118 | } |
| 119 | |
| 120 | int do_sys_class_power_supply(int update_every, usec_t dt) { |
| 121 | (void)dt; |
| 122 | static int do_capacity = -1, do_power = -1, do_property[3] = {-1}; |
| 123 | static int keep_fds_open = CONFIG_BOOLEAN_NO, keep_fds_open_config = -1; |
| 124 | static const char *dirname = NULL; |
| 125 | |
| 126 | if(unlikely(do_capacity == -1)) { |
| 127 | do_capacity = inicfg_get_boolean(&netdata_config, "plugin:proc:/sys/class/power_supply", "battery capacity", CONFIG_BOOLEAN_YES); |
| 128 | do_power = inicfg_get_boolean(&netdata_config, "plugin:proc:/sys/class/power_supply", "battery power", CONFIG_BOOLEAN_YES); |
| 129 | do_property[0] = inicfg_get_boolean(&netdata_config, "plugin:proc:/sys/class/power_supply", "battery charge", CONFIG_BOOLEAN_NO); |
| 130 | do_property[1] = inicfg_get_boolean(&netdata_config, "plugin:proc:/sys/class/power_supply", "battery energy", CONFIG_BOOLEAN_NO); |
| 131 | do_property[2] = inicfg_get_boolean(&netdata_config, "plugin:proc:/sys/class/power_supply", "power supply voltage", CONFIG_BOOLEAN_NO); |
| 132 | |
| 133 | keep_fds_open_config = inicfg_get_boolean_ondemand(&netdata_config, "plugin:proc:/sys/class/power_supply", "keep files open", CONFIG_BOOLEAN_AUTO); |
| 134 | |
| 135 | char filename[FILENAME_MAX + 1]; |
| 136 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/class/power_supply"); |
| 137 | dirname = inicfg_get(&netdata_config, "plugin:proc:/sys/class/power_supply", "directory to monitor", filename); |
| 138 | } |
| 139 | |
| 140 | DIR *dir = opendir(dirname); |
| 141 | if(unlikely(!dir)) { |
| 142 | collector_error("Cannot read directory '%s'", dirname); |
| 143 | return 1; |
| 144 | } |
| 145 | |
| 146 | struct dirent *de = NULL; |
| 147 | while(likely(de = readdir(dir))) { |
| 148 | if(likely(de->d_type == DT_DIR |
| 149 | && ( |
| 150 | (de->d_name[0] == '.' && de->d_name[1] == '\0') |
| 151 | || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') |
| 152 | ))) |
| 153 | continue; |
| 154 | |
| 155 | if(likely(de->d_type == DT_LNK || de->d_type == DT_DIR)) { |
| 156 | uint32_t hash = simple_hash(de->d_name); |
| 157 | |
| 158 | struct power_supply *ps; |
| 159 | for(ps = power_supply_root; ps; ps = ps->next) { |
| 160 | if(unlikely(ps->hash == hash && !strcmp(ps->name, de->d_name))) { |
| 161 | ps->found = 1; |
| 162 | break; |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | // allocate memory for power supply and initialize it |
| 167 | if(unlikely(!ps)) { |
| 168 | ps = callocz(sizeof(struct power_supply), 1); |
| 169 | ps->name = strdupz(de->d_name); |
| 170 | ps->hash = simple_hash(de->d_name); |
| 171 | ps->found = 1; |
| 172 | ps->next = power_supply_root; |
| 173 | power_supply_root = ps; |
| 174 | |
| 175 | struct stat stbuf; |
| 176 | if(likely(do_capacity != CONFIG_BOOLEAN_NO)) { |
| 177 | char filename[FILENAME_MAX + 1]; |
| 178 | snprintfz(filename, FILENAME_MAX, "%s/%s/%s", dirname, de->d_name, "capacity"); |
| 179 | if (stat(filename, &stbuf) == 0) { |
| 180 | ps->capacity = callocz(sizeof(struct simple_property), 1); |
| 181 | ps->capacity->filename = strdupz(filename); |
| 182 | ps->capacity->fd = -1; |
| 183 | files_num++; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | if(likely(do_power != CONFIG_BOOLEAN_NO)) { |
| 188 | char filename[FILENAME_MAX + 1]; |
| 189 | snprintfz(filename, FILENAME_MAX, "%s/%s/%s", dirname, de->d_name, "power_now"); |
| 190 | if (stat(filename, &stbuf) == 0) { |
| 191 | ps->power = callocz(sizeof(struct simple_property), 1); |
| 192 | ps->power->filename = strdupz(filename); |
| 193 | ps->power->fd = -1; |
| 194 | files_num++; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | // allocate memory and initialize structures for every property and file found |
| 199 | size_t pr_idx, pd_idx; |
| 200 | size_t prev_idx = 3; // there is no property with this index |
| 201 | |
| 202 | for(pr_idx = 0; pr_idx < 3; pr_idx++) { |
| 203 | if(unlikely(do_property[pr_idx] != CONFIG_BOOLEAN_NO)) { |
| 204 | struct ps_property *pr = NULL; |
| 205 | int min_value_found = 0, max_value_found = 0; |
| 206 | |
| 207 | for(pd_idx = pr_idx * 5; pd_idx < pr_idx * 5 + 5; pd_idx++) { |
| 208 | |
| 209 | // check if file exists |
| 210 | char filename[FILENAME_MAX + 1]; |
| 211 | snprintfz(filename, FILENAME_MAX, "%s/%s/%s_%s", dirname, de->d_name, |
| 212 | ps_property_names[pr_idx], ps_property_dim_names[pd_idx]); |
| 213 | if (stat(filename, &stbuf) == 0) { |
| 214 | |
| 215 | if(unlikely(pd_idx == pr_idx * 5 + 1)) |
| 216 | min_value_found = 1; |
| 217 | if(unlikely(pd_idx == pr_idx * 5 + 3)) |
| 218 | max_value_found = 1; |
| 219 | |
| 220 | // add chart |
| 221 | if(unlikely(prev_idx != pr_idx)) { |
| 222 | pr = callocz(sizeof(struct ps_property), 1); |
| 223 | pr->name = strdupz(ps_property_names[pr_idx]); |
| 224 | pr->title = strdupz(ps_property_titles[pr_idx]); |
| 225 | pr->units = strdupz(ps_property_units[pr_idx]); |
| 226 | pr->priority = ps_property_priorities[pr_idx]; |
| 227 | prev_idx = pr_idx; |
| 228 | pr->next = ps->property_root; |
| 229 | ps->property_root = pr; |
| 230 | } |
| 231 | |
| 232 | // add dimension |
| 233 | struct ps_property_dim *pd; |
| 234 | pd= callocz(sizeof(struct ps_property_dim), 1); |
| 235 | pd->name = strdupz(ps_property_dim_names[pd_idx]); |
| 236 | pd->filename = strdupz(filename); |
| 237 | pd->fd = -1; |
| 238 | files_num++; |
| 239 | pd->next = pr->property_dim_root; |
| 240 | pr->property_dim_root = pd; |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | // create a zero empty/min dimension |
| 245 | if(unlikely(max_value_found && !min_value_found)) { |
| 246 | struct ps_property_dim *pd; |
| 247 | pd= callocz(sizeof(struct ps_property_dim), 1); |
| 248 | pd->name = strdupz(ps_property_dim_names[pr_idx * 5 + 1]); |
| 249 | pd->always_zero = 1; |
| 250 | pd->next = pr->property_dim_root; |
| 251 | pr->property_dim_root = pd; |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | // read capacity file |
| 258 | if(likely(ps->capacity)) { |
| 259 | read_simple_property(ps->capacity, keep_fds_open); |
| 260 | } |
| 261 | |
| 262 | // read power file |
| 263 | if(likely(ps->power)) { |
| 264 | read_simple_property(ps->power, keep_fds_open); |
| 265 | } |
| 266 | |
| 267 | if(unlikely((!ps->power || !ps->power->ok) && (!ps->capacity || !ps->capacity->ok))) { |
| 268 | power_supply_free(ps); |
| 269 | ps = NULL; |
| 270 | } |
| 271 | |
| 272 | // read property files |
| 273 | int read_error = 0; |
| 274 | struct ps_property *pr; |
| 275 | if (likely(ps)) |
| 276 | { |
| 277 | for(pr = ps->property_root; pr && !read_error; ) { |
| 278 | struct ps_property *next = pr->next; |
| 279 | struct ps_property_dim *pd; |
| 280 | for(pd = pr->property_dim_root; pd; pd = pd->next) { |
| 281 | if(likely(!pd->always_zero)) { |
| 282 | char buffer[PROP_VALUE_LENGTH_MAX + 1]; |
| 283 | |
| 284 | if(unlikely(pd->fd == -1)) { |
| 285 | pd->fd = open(pd->filename, O_RDONLY | O_CLOEXEC, 0666); |
| 286 | if(unlikely(pd->fd == -1)) { |
| 287 | collector_error("Cannot open file '%s'", pd->filename); |
| 288 | read_error = 1; |
| 289 | power_supply_free(ps); |
| 290 | ps = NULL; |
| 291 | break; |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | ssize_t r = read(pd->fd, buffer, PROP_VALUE_LENGTH_MAX); |
| 296 | if(unlikely(r < 1)) { |
| 297 | collector_error("Cannot read file '%s'", pd->filename); |
| 298 | read_error = 1; |
| 299 | power_supply_free(ps); |
| 300 | ps = NULL; |
| 301 | break; |
| 302 | } |
| 303 | buffer[r] = '\0'; |
| 304 | pd->value = str2ull(buffer, NULL); |
| 305 | |
| 306 | if(unlikely(!keep_fds_open)) { |
| 307 | close(pd->fd); |
| 308 | pd->fd = -1; |
| 309 | } |
| 310 | else if(unlikely(lseek(pd->fd, 0, SEEK_SET) == -1)) { |
| 311 | collector_error("Cannot seek in file '%s'", pd->filename); |
| 312 | close(pd->fd); |
| 313 | pd->fd = -1; |
| 314 | } |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | if(unlikely(read_error)) |
| 319 | break; |
| 320 | |
| 321 | pr = next; |
| 322 | } |
| 323 | } |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | closedir(dir); |
| 328 | |
| 329 | keep_fds_open = keep_fds_open_config; |
| 330 | if(likely(keep_fds_open_config == CONFIG_BOOLEAN_AUTO)) { |
| 331 | if(unlikely(files_num > 32)) |
| 332 | keep_fds_open = CONFIG_BOOLEAN_NO; |
| 333 | else |
| 334 | keep_fds_open = CONFIG_BOOLEAN_YES; |
| 335 | } |
| 336 | |
| 337 | // -------------------------------------------------------------------- |
| 338 | |
| 339 | struct power_supply *ps = power_supply_root; |
| 340 | while(unlikely(ps)) { |
| 341 | if(unlikely(!ps->found)) { |
| 342 | struct power_supply *f = ps; |
| 343 | ps = ps->next; |
| 344 | power_supply_free(f); |
| 345 | continue; |
| 346 | } |
| 347 | |
| 348 | if(likely(ps->capacity && ps->capacity->ok)) { |
| 349 | rrdset_create_simple_prop(ps, ps->capacity, "Battery capacity", "capacity", 1, "percentage", NETDATA_CHART_PRIO_POWER_SUPPLY_CAPACITY, update_every); |
| 350 | } |
| 351 | |
| 352 | if(likely(ps->power && ps->power->ok)) { |
| 353 | rrdset_create_simple_prop(ps, ps->power, "Battery power", "power", 1000000, "W", NETDATA_CHART_PRIO_POWER_SUPPLY_POWER, update_every); |
| 354 | } |
| 355 | |
| 356 | struct ps_property *pr; |
| 357 | for(pr = ps->property_root; pr; pr = pr->next) { |
| 358 | if(unlikely(!pr->st)) { |
| 359 | char id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1]; |
| 360 | snprintfz(id, RRD_ID_LENGTH_MAX, "powersupply_%s", pr->name); |
| 361 | snprintfz(context, RRD_ID_LENGTH_MAX, "powersupply.%s", pr->name); |
| 362 | |
| 363 | pr->st = rrdset_create_localhost( |
| 364 | id |
| 365 | , ps->name |
| 366 | , NULL |
| 367 | , pr->name |
| 368 | , context |
| 369 | , pr->title |
| 370 | , pr->units |
| 371 | , PLUGIN_PROC_NAME |
| 372 | , PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME |
| 373 | , pr->priority |
| 374 | , update_every |
| 375 | , RRDSET_TYPE_LINE |
| 376 | ); |
| 377 | |
| 378 | add_labels_to_power_supply(ps, pr->st); |
| 379 | } |
| 380 | |
| 381 | struct ps_property_dim *pd; |
| 382 | for(pd = pr->property_dim_root; pd; pd = pd->next) { |
| 383 | if(unlikely(!pd->rd)) pd->rd = rrddim_add(pr->st, pd->name, NULL, 1, 1000000, RRD_ALGORITHM_ABSOLUTE); |
| 384 | rrddim_set_by_pointer(pr->st, pd->rd, pd->value); |
| 385 | } |
| 386 | |
| 387 | rrdset_done(pr->st); |
| 388 | } |
| 389 | |
| 390 | ps->found = 0; |
| 391 | ps = ps->next; |
| 392 | } |
| 393 | |
| 394 | return 0; |
| 395 | } |