master
c 395 lines 15.6 KB
Raw
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 }