1 /**
2 * collectd - src/battery.c
3 * Copyright (C) 2006 Florian octo Forster
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Authors:
20 * Florian octo Forster <octo at verplant.org>
21 **/
23 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_debug.h"
28 #define MODULE_NAME "battery"
29 #define BUFSIZE 512
31 #if HAVE_MACH_MACH_TYPES_H
32 # include <mach/mach_types.h>
33 #endif
34 #if HAVE_MACH_MACH_INIT_H
35 # include <mach/mach_init.h>
36 #endif
37 #if HAVE_MACH_MACH_ERROR_H
38 # include <mach/mach_error.h>
39 #endif
40 #if HAVE_COREFOUNDATION_COREFOUNDATION_H
41 # include <CoreFoundation/CoreFoundation.h>
42 #endif
43 #if HAVE_IOKIT_IOKITLIB_H
44 # include <IOKit/IOKitLib.h>
45 #endif
46 #if HAVE_IOKIT_IOTYPES_H
47 # include <IOKit/IOTypes.h>
48 #endif
49 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
50 # include <IOKit/ps/IOPowerSources.h>
51 #endif
52 #if HAVE_IOKIT_PS_IOPSKEYS_H
53 # include <IOKit/ps/IOPSKeys.h>
54 #endif
56 #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H || KERNEL_LINUX
57 # define BATTERY_HAVE_READ 1
58 #else
59 # define BATTERY_HAVE_READ 0
60 #endif
62 #define INVALID_VALUE 47841.29
64 static char *battery_current_file = "battery-%s/current.rrd";
65 static char *battery_voltage_file = "battery-%s/voltage.rrd";
66 static char *battery_charge_file = "battery-%s/charge.rrd";
68 static char *ds_def_current[] =
69 {
70 "DS:current:GAUGE:"COLLECTD_HEARTBEAT":U:U",
71 NULL
72 };
73 static int ds_num_current = 1;
75 static char *ds_def_voltage[] =
76 {
77 "DS:voltage:GAUGE:"COLLECTD_HEARTBEAT":U:U",
78 NULL
79 };
80 static int ds_num_voltage = 1;
82 static char *ds_def_charge[] =
83 {
84 "DS:charge:GAUGE:"COLLECTD_HEARTBEAT":0:U",
85 NULL
86 };
87 static int ds_num_charge = 1;
89 #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
90 /* No global variables */
91 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
93 #elif KERNEL_LINUX
94 static int battery_pmu_num = 0;
95 static char *battery_pmu_file = "/proc/pmu/battery_%i";
96 #endif /* KERNEL_LINUX */
98 static void battery_init (void)
99 {
100 #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
101 /* No init neccessary */
102 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
104 #elif KERNEL_LINUX
105 int len;
106 char filename[BUFSIZE];
108 for (battery_pmu_num = 0; ; battery_pmu_num++)
109 {
110 len = snprintf (filename, BUFSIZE, battery_pmu_file, battery_pmu_num);
112 if ((len >= BUFSIZE) || (len < 0))
113 break;
115 if (access (filename, R_OK))
116 break;
117 }
118 #endif /* KERNEL_LINUX */
120 return;
121 }
123 static void battery_current_write (char *host, char *inst, char *val)
124 {
125 char filename[BUFSIZE];
126 int len;
128 len = snprintf (filename, BUFSIZE, battery_current_file, inst);
129 if ((len >= BUFSIZE) || (len < 0))
130 return;
132 rrd_update_file (host, filename, val,
133 ds_def_current, ds_num_current);
134 }
136 static void battery_voltage_write (char *host, char *inst, char *val)
137 {
138 char filename[BUFSIZE];
139 int len;
141 len = snprintf (filename, BUFSIZE, battery_voltage_file, inst);
142 if ((len >= BUFSIZE) || (len < 0))
143 return;
145 rrd_update_file (host, filename, val,
146 ds_def_voltage, ds_num_voltage);
147 }
149 static void battery_charge_write (char *host, char *inst, char *val)
150 {
151 char filename[BUFSIZE];
152 int len;
154 len = snprintf (filename, BUFSIZE, battery_charge_file, inst);
155 if ((len >= BUFSIZE) || (len < 0))
156 return;
158 rrd_update_file (host, filename, val,
159 ds_def_charge, ds_num_charge);
160 }
162 #if BATTERY_HAVE_READ
163 static void battery_submit (char *inst, double current, double voltage, double charge)
164 {
165 int len;
166 char buffer[BUFSIZE];
168 if (current != INVALID_VALUE)
169 {
170 len = snprintf (buffer, BUFSIZE, "N:%.3f", current);
172 if ((len > 0) && (len < BUFSIZE))
173 plugin_submit ("battery_current", inst, buffer);
174 }
175 else
176 {
177 plugin_submit ("battery_current", inst, "N:U");
178 }
180 if (voltage != INVALID_VALUE)
181 {
182 len = snprintf (buffer, BUFSIZE, "N:%.3f", voltage);
184 if ((len > 0) && (len < BUFSIZE))
185 plugin_submit ("battery_voltage", inst, buffer);
186 }
187 else
188 {
189 plugin_submit ("battery_voltage", inst, "N:U");
190 }
192 if (charge != INVALID_VALUE)
193 {
194 len = snprintf (buffer, BUFSIZE, "N:%.3f", charge);
196 if ((len > 0) && (len < BUFSIZE))
197 plugin_submit ("battery_charge", inst, buffer);
198 }
199 else
200 {
201 plugin_submit ("battery_charge", inst, "N:U");
202 }
203 }
205 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H
206 double dict_get_double (CFDictionaryRef dict, char *key_string)
207 {
208 double val_double;
209 long long val_int;
210 CFNumberRef val_obj;
211 CFStringRef key_obj;
213 key_obj = CFStringCreateWithCString (kCFAllocatorDefault, key_string,
214 kCFStringEncodingASCII);
215 if (key_obj == NULL)
216 {
217 DBG ("CFStringCreateWithCString (%s) failed.\n", key_string);
218 return (INVALID_VALUE);
219 }
221 if ((val_obj = CFDictionaryGetValue (dict, key_obj)) == NULL)
222 {
223 DBG ("CFDictionaryGetValue (%s) failed.", key_string);
224 CFRelease (key_obj);
225 return (INVALID_VALUE);
226 }
227 CFRelease (key_obj);
229 if (CFGetTypeID (val_obj) == CFNumberGetTypeID ())
230 {
231 if (CFNumberIsFloatType (val_obj))
232 {
233 CFNumberGetValue (val_obj,
234 kCFNumberDoubleType,
235 &val_double);
236 }
237 else
238 {
239 CFNumberGetValue (val_obj,
240 kCFNumberLongLongType,
241 &val_int);
242 val_double = val_int;
243 }
244 }
245 else
246 {
247 DBG ("CFGetTypeID (val_obj) = %i", (int) CFGetTypeID (val_obj));
248 return (INVALID_VALUE);
249 }
251 return (val_double);
252 }
253 #endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H */
255 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
256 static void get_via_io_power_sources (double *ret_charge,
257 double *ret_current,
258 double *ret_voltage)
259 {
260 CFTypeRef ps_raw;
261 CFArrayRef ps_array;
262 int ps_array_len;
263 CFDictionaryRef ps_dict;
264 CFTypeRef ps_obj;
266 double temp_double;
267 int i;
269 ps_raw = IOPSCopyPowerSourcesInfo ();
270 ps_array = IOPSCopyPowerSourcesList (ps_raw);
271 ps_array_len = CFArrayGetCount (ps_array);
273 DBG ("ps_array_len == %i", ps_array_len);
275 for (i = 0; i < ps_array_len; i++)
276 {
277 ps_obj = CFArrayGetValueAtIndex (ps_array, i);
278 ps_dict = IOPSGetPowerSourceDescription (ps_raw, ps_obj);
280 if (ps_dict == NULL)
281 {
282 DBG ("IOPSGetPowerSourceDescription failed.");
283 continue;
284 }
286 if (CFGetTypeID (ps_dict) != CFDictionaryGetTypeID ())
287 {
288 DBG ("IOPSGetPowerSourceDescription did not return a CFDictionaryRef");
289 continue;
290 }
292 /* FIXME: Check if this is really an internal battery */
294 if (*ret_charge == INVALID_VALUE)
295 {
296 /* This is the charge in percent. */
297 temp_double = dict_get_double (ps_dict,
298 kIOPSCurrentCapacityKey);
299 if ((temp_double != INVALID_VALUE)
300 && (temp_double >= 0.0)
301 && (temp_double <= 100.0))
302 *ret_charge = temp_double;
303 }
305 if (*ret_current == INVALID_VALUE)
306 {
307 temp_double = dict_get_double (ps_dict,
308 kIOPSCurrentKey);
309 if (temp_double != INVALID_VALUE)
310 *ret_current = temp_double / 1000.0;
311 }
313 if (*ret_voltage == INVALID_VALUE)
314 {
315 temp_double = dict_get_double (ps_dict,
316 kIOPSVoltageKey);
317 if (temp_double != INVALID_VALUE)
318 *ret_voltage = temp_double / 1000.0;
319 }
320 }
322 CFRelease(ps_array);
323 CFRelease(ps_raw);
324 }
325 #endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
327 #if HAVE_IOKIT_IOKITLIB_H
328 static void get_via_generic_iokit (double *ret_charge,
329 double *ret_current,
330 double *ret_voltage)
331 {
332 kern_return_t status;
333 io_iterator_t iterator;
334 io_object_t io_obj;
336 CFDictionaryRef bat_root_dict;
337 CFArrayRef bat_info_arry;
338 CFIndex bat_info_arry_len;
339 CFIndex bat_info_arry_pos;
340 CFDictionaryRef bat_info_dict;
342 double temp_double;
344 status = IOServiceGetMatchingServices (kIOMasterPortDefault,
345 IOServiceNameMatching ("battery"),
346 &iterator);
347 if (status != kIOReturnSuccess)
348 {
349 DBG ("IOServiceGetMatchingServices failed.");
350 return;
351 }
353 while ((io_obj = IOIteratorNext (iterator)))
354 {
355 status = IORegistryEntryCreateCFProperties (io_obj,
356 (CFMutableDictionaryRef *) &bat_root_dict,
357 kCFAllocatorDefault,
358 kNilOptions);
359 if (status != kIOReturnSuccess)
360 {
361 DBG ("IORegistryEntryCreateCFProperties failed.");
362 continue;
363 }
365 bat_info_arry = (CFArrayRef) CFDictionaryGetValue (bat_root_dict,
366 CFSTR ("IOBatteryInfo"));
367 if (bat_info_arry == NULL)
368 {
369 CFRelease (bat_root_dict);
370 continue;
371 }
372 bat_info_arry_len = CFArrayGetCount (bat_info_arry);
374 for (bat_info_arry_pos = 0;
375 bat_info_arry_pos < bat_info_arry_len;
376 bat_info_arry_pos++)
377 {
378 bat_info_dict = (CFDictionaryRef) CFArrayGetValueAtIndex (bat_info_arry, bat_info_arry_pos);
380 if (*ret_charge == INVALID_VALUE)
381 {
382 temp_double = dict_get_double (bat_info_dict,
383 "Capacity");
384 if (temp_double != INVALID_VALUE)
385 *ret_charge = temp_double / 1000.0;
386 }
388 if (*ret_current == INVALID_VALUE)
389 {
390 temp_double = dict_get_double (bat_info_dict,
391 "Current");
392 if (temp_double != INVALID_VALUE)
393 *ret_current = temp_double / 1000.0;
394 }
396 if (*ret_voltage == INVALID_VALUE)
397 {
398 temp_double = dict_get_double (bat_info_dict,
399 "Voltage");
400 if (temp_double != INVALID_VALUE)
401 *ret_voltage = temp_double / 1000.0;
402 }
403 }
405 CFRelease (bat_root_dict);
406 }
408 IOObjectRelease (iterator);
409 }
410 #endif /* HAVE_IOKIT_IOKITLIB_H */
412 static void battery_read (void)
413 {
414 #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
415 double charge = INVALID_VALUE; /* Current charge in Ah */
416 double current = INVALID_VALUE; /* Current in A */
417 double voltage = INVALID_VALUE; /* Voltage in V */
419 double charge_rel = INVALID_VALUE; /* Current charge in percent */
420 double charge_abs = INVALID_VALUE; /* Total capacity */
422 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
423 get_via_io_power_sources (&charge_rel, ¤t, &voltage);
424 #endif
425 #if HAVE_IOKIT_IOKITLIB_H
426 get_via_generic_iokit (&charge_abs, ¤t, &voltage);
427 #endif
429 if ((charge_rel != INVALID_VALUE) && (charge_abs != INVALID_VALUE))
430 charge = charge_abs * charge_rel / 100.0;
432 if ((charge != INVALID_VALUE)
433 || (current != INVALID_VALUE)
434 || (voltage != INVALID_VALUE))
435 battery_submit ("0", current, voltage, charge);
436 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
438 #elif KERNEL_LINUX
439 FILE *fh;
440 char buffer[BUFSIZE];
441 char filename[BUFSIZE];
443 char *fields[8];
444 int numfields;
446 int i;
447 int len;
449 for (i = 0; i < battery_pmu_num; i++)
450 {
451 char batnum_str[BUFSIZE];
452 double current = INVALID_VALUE;
453 double voltage = INVALID_VALUE;
454 double charge = INVALID_VALUE;
455 double *valptr = NULL;
457 len = snprintf (filename, BUFSIZE, battery_pmu_file, i);
458 if ((len >= BUFSIZE) || (len < 0))
459 continue;
461 len = snprintf (batnum_str, BUFSIZE, "%i", i);
462 if ((len >= BUFSIZE) || (len < 0))
463 continue;
465 if ((fh = fopen (filename, "r")) == NULL)
466 continue;
468 while (fgets (buffer, BUFSIZE, fh) != NULL)
469 {
470 numfields = strsplit (buffer, fields, 8);
472 if (numfields < 3)
473 continue;
475 if (strcmp ("current", fields[0]) == 0)
476 valptr = ¤t;
477 else if (strcmp ("voltage", fields[0]) == 0)
478 valptr = &voltage;
479 else if (strcmp ("charge", fields[0]) == 0)
480 valptr = &charge;
481 else
482 valptr = NULL;
484 if (valptr != NULL)
485 {
486 char *endptr;
488 endptr = NULL;
489 errno = 0;
491 *valptr = strtod (fields[2], &endptr) / 1000.0;
493 if ((fields[2] == endptr) || (errno != 0))
494 *valptr = INVALID_VALUE;
495 }
496 }
498 if ((current != INVALID_VALUE)
499 || (voltage != INVALID_VALUE)
500 || (charge != INVALID_VALUE))
501 battery_submit (batnum_str, current, voltage, charge);
503 fclose (fh);
504 fh = NULL;
505 }
507 if (access ("/proc/acpi/battery", R_OK | X_OK) == 0)
508 {
509 double current = INVALID_VALUE;
510 double voltage = INVALID_VALUE;
511 double charge = INVALID_VALUE;
512 double *valptr = NULL;
513 int charging = 0;
515 struct dirent *ent;
516 DIR *dh;
518 if ((dh = opendir ("/proc/acpi/battery")) == NULL)
519 {
520 syslog (LOG_ERR, "Cannot open `/proc/acpi/battery': %s", strerror (errno));
521 return;
522 }
524 while ((ent = readdir (dh)) != NULL)
525 {
526 if (ent->d_name[0] == '.')
527 continue;
529 len = snprintf (filename, BUFSIZE, "/proc/acpi/battery/%s/state", ent->d_name);
530 if ((len >= BUFSIZE) || (len < 0))
531 continue;
533 if ((fh = fopen (filename, "r")) == NULL)
534 {
535 syslog (LOG_ERR, "Cannot open `%s': %s", filename, strerror (errno));
536 continue;
537 }
539 /*
540 * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
541 * [11:00] <@tokkee> present: yes
542 * [11:00] <@tokkee> capacity state: ok
543 * [11:00] <@tokkee> charging state: charging
544 * [11:00] <@tokkee> present rate: 1724 mA
545 * [11:00] <@tokkee> remaining capacity: 4136 mAh
546 * [11:00] <@tokkee> present voltage: 12428 mV
547 */
548 while (fgets (buffer, BUFSIZE, fh) != NULL)
549 {
550 numfields = strsplit (buffer, fields, 8);
552 if (numfields < 3)
553 continue;
555 if ((strcmp (fields[0], "present") == 0)
556 && (strcmp (fields[1], "rate:") == 0))
557 valptr = ¤t;
558 else if ((strcmp (fields[0], "remaining") == 0)
559 && (strcmp (fields[1], "capacity:") == 0))
560 valptr = &charge;
561 else if ((strcmp (fields[0], "present") == 0)
562 && (strcmp (fields[1], "voltage:") == 0))
563 valptr = &voltage;
564 else
565 valptr = NULL;
567 if ((strcmp (fields[0], "charging") == 0)
568 && (strcmp (fields[1], "state:") == 0))
569 {
570 if (strcmp (fields[2], "charging") == 0)
571 charging = 1;
572 else
573 charging = 0;
574 }
576 if (valptr != NULL)
577 {
578 char *endptr;
580 endptr = NULL;
581 errno = 0;
583 *valptr = strtod (fields[2], &endptr) / 1000.0;
585 if ((fields[2] == endptr) || (errno != 0))
586 *valptr = INVALID_VALUE;
587 }
588 }
590 if ((current != INVALID_VALUE) && (charging == 0))
591 current *= -1;
593 if ((current != INVALID_VALUE)
594 || (voltage != INVALID_VALUE)
595 || (charge != INVALID_VALUE))
596 battery_submit (ent->d_name, current, voltage, charge);
598 fclose (fh);
599 }
601 closedir (dh);
602 }
603 #endif /* KERNEL_LINUX */
604 }
605 #else
606 # define battery_read NULL
607 #endif /* BATTERY_HAVE_READ */
609 void module_register (void)
610 {
611 plugin_register (MODULE_NAME, battery_init, battery_read, NULL);
612 plugin_register ("battery_current", NULL, NULL, battery_current_write);
613 plugin_register ("battery_voltage", NULL, NULL, battery_voltage_write);
614 plugin_register ("battery_charge", NULL, NULL, battery_charge_write);
615 }
617 #undef BUFSIZE
618 #undef MODULE_NAME