1d0757d3796accc53845a6d433188114b30ac7bd
[collectd.git] / src / battery.c
1 /**
2  * collectd - src/battery.c
3  * Copyright (C) 2006-2014  Florian octo Forster
4  * Copyright (C) 2008       Michał Mirosław
5  * Copyright (C) 2014       Andy Parkins
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation; only version 2 of the License is applicable.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  *
20  * Authors:
21  *   Florian octo Forster <octo at collectd.org>
22  *   Michał Mirosław <mirq-linux at rere.qmqm.pl>
23  *   Andy Parkins <andyp at fussylogic.co.uk>
24  **/
25
26 #include "collectd.h"
27 #include "common.h"
28 #include "plugin.h"
29
30 #include "utils_complain.h"
31
32 #if HAVE_MACH_MACH_TYPES_H
33 #  include <mach/mach_types.h>
34 #endif
35 #if HAVE_MACH_MACH_INIT_H
36 #  include <mach/mach_init.h>
37 #endif
38 #if HAVE_MACH_MACH_ERROR_H
39 #  include <mach/mach_error.h>
40 #endif
41 #if HAVE_COREFOUNDATION_COREFOUNDATION_H
42 #  include <CoreFoundation/CoreFoundation.h>
43 #endif
44 #if HAVE_IOKIT_IOKITLIB_H
45 #  include <IOKit/IOKitLib.h>
46 #endif
47 #if HAVE_IOKIT_IOTYPES_H
48 #  include <IOKit/IOTypes.h>
49 #endif
50 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
51 #  include <IOKit/ps/IOPowerSources.h>
52 #endif
53 #if HAVE_IOKIT_PS_IOPSKEYS_H
54 #  include <IOKit/ps/IOPSKeys.h>
55 #endif
56
57 #if !HAVE_IOKIT_IOKITLIB_H && !HAVE_IOKIT_PS_IOPOWERSOURCES_H && !KERNEL_LINUX
58 # error "No applicable input method."
59 #endif
60
61 #define INVALID_VALUE 47841.29
62
63 #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
64         /* No global variables */
65 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
66
67 #elif KERNEL_LINUX
68 # define PROC_PMU_PATH_FORMAT "/proc/pmu/battery_%i"
69 # define PROC_ACPI_PATH "/proc/acpi/battery"
70 # define SYSFS_PATH "/sys/class/power_supply"
71 #endif /* KERNEL_LINUX */
72
73 static void battery_submit (char const *plugin_instance, /* {{{ */
74                 const char *type, gauge_t value)
75 {
76         value_t values[1];
77         value_list_t vl = VALUE_LIST_INIT;
78
79         values[0].gauge = value;
80
81         vl.values = values;
82         vl.values_len = 1;
83         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
84         sstrncpy (vl.plugin, "battery", sizeof (vl.plugin));
85         sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
86         sstrncpy (vl.type, type, sizeof (vl.type));
87
88         plugin_dispatch_values (&vl);
89 } /* }}} void battery_submit */
90
91 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H
92 static double dict_get_double (CFDictionaryRef dict, char *key_string) /* {{{ */
93 {
94         double      val_double;
95         long long   val_int;
96         CFNumberRef val_obj;
97         CFStringRef key_obj;
98
99         key_obj = CFStringCreateWithCString (kCFAllocatorDefault, key_string,
100                         kCFStringEncodingASCII);
101         if (key_obj == NULL)
102         {
103                 DEBUG ("CFStringCreateWithCString (%s) failed.\n", key_string);
104                 return (INVALID_VALUE);
105         }
106
107         if ((val_obj = CFDictionaryGetValue (dict, key_obj)) == NULL)
108         {
109                 DEBUG ("CFDictionaryGetValue (%s) failed.", key_string);
110                 CFRelease (key_obj);
111                 return (INVALID_VALUE);
112         }
113         CFRelease (key_obj);
114
115         if (CFGetTypeID (val_obj) == CFNumberGetTypeID ())
116         {
117                 if (CFNumberIsFloatType (val_obj))
118                 {
119                         CFNumberGetValue (val_obj,
120                                         kCFNumberDoubleType,
121                                         &val_double);
122                 }
123                 else
124                 {
125                         CFNumberGetValue (val_obj,
126                                         kCFNumberLongLongType,
127                                         &val_int);
128                         val_double = val_int;
129                 }
130         }
131         else
132         {
133                 DEBUG ("CFGetTypeID (val_obj) = %i", (int) CFGetTypeID (val_obj));
134                 return (INVALID_VALUE);
135         }
136
137         return (val_double);
138 } /* }}} double dict_get_double */
139
140 # if HAVE_IOKIT_PS_IOPOWERSOURCES_H
141 static void get_via_io_power_sources (double *ret_charge, /* {{{ */
142                 double *ret_current,
143                 double *ret_voltage)
144 {
145         CFTypeRef       ps_raw;
146         CFArrayRef      ps_array;
147         int             ps_array_len;
148         CFDictionaryRef ps_dict;
149         CFTypeRef       ps_obj;
150
151         double temp_double;
152         int i;
153
154         ps_raw       = IOPSCopyPowerSourcesInfo ();
155         ps_array     = IOPSCopyPowerSourcesList (ps_raw);
156         ps_array_len = CFArrayGetCount (ps_array);
157
158         DEBUG ("ps_array_len == %i", ps_array_len);
159
160         for (i = 0; i < ps_array_len; i++)
161         {
162                 ps_obj  = CFArrayGetValueAtIndex (ps_array, i);
163                 ps_dict = IOPSGetPowerSourceDescription (ps_raw, ps_obj);
164
165                 if (ps_dict == NULL)
166                 {
167                         DEBUG ("IOPSGetPowerSourceDescription failed.");
168                         continue;
169                 }
170
171                 if (CFGetTypeID (ps_dict) != CFDictionaryGetTypeID ())
172                 {
173                         DEBUG ("IOPSGetPowerSourceDescription did not return a CFDictionaryRef");
174                         continue;
175                 }
176
177                 /* FIXME: Check if this is really an internal battery */
178
179                 if (*ret_charge == INVALID_VALUE)
180                 {
181                         /* This is the charge in percent. */
182                         temp_double = dict_get_double (ps_dict,
183                                         kIOPSCurrentCapacityKey);
184                         if ((temp_double != INVALID_VALUE)
185                                         && (temp_double >= 0.0)
186                                         && (temp_double <= 100.0))
187                                 *ret_charge = temp_double;
188                 }
189
190                 if (*ret_current == INVALID_VALUE)
191                 {
192                         temp_double = dict_get_double (ps_dict,
193                                         kIOPSCurrentKey);
194                         if (temp_double != INVALID_VALUE)
195                                 *ret_current = temp_double / 1000.0;
196                 }
197
198                 if (*ret_voltage == INVALID_VALUE)
199                 {
200                         temp_double = dict_get_double (ps_dict,
201                                         kIOPSVoltageKey);
202                         if (temp_double != INVALID_VALUE)
203                                 *ret_voltage = temp_double / 1000.0;
204                 }
205         }
206
207         CFRelease(ps_array);
208         CFRelease(ps_raw);
209 } /* }}} void get_via_io_power_sources */
210 # endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
211
212 # if HAVE_IOKIT_IOKITLIB_H
213 static void get_via_generic_iokit (double *ret_charge, /* {{{ */
214                 double *ret_current,
215                 double *ret_voltage)
216 {
217         kern_return_t   status;
218         io_iterator_t   iterator;
219         io_object_t     io_obj;
220
221         CFDictionaryRef bat_root_dict;
222         CFArrayRef      bat_info_arry;
223         CFIndex         bat_info_arry_len;
224         CFIndex         bat_info_arry_pos;
225         CFDictionaryRef bat_info_dict;
226
227         double temp_double;
228
229         status = IOServiceGetMatchingServices (kIOMasterPortDefault,
230                         IOServiceNameMatching ("battery"),
231                         &iterator);
232         if (status != kIOReturnSuccess)
233         {
234                 DEBUG ("IOServiceGetMatchingServices failed.");
235                 return;
236         }
237
238         while ((io_obj = IOIteratorNext (iterator)))
239         {
240                 status = IORegistryEntryCreateCFProperties (io_obj,
241                                 (CFMutableDictionaryRef *) &bat_root_dict,
242                                 kCFAllocatorDefault,
243                                 kNilOptions);
244                 if (status != kIOReturnSuccess)
245                 {
246                         DEBUG ("IORegistryEntryCreateCFProperties failed.");
247                         continue;
248                 }
249
250                 bat_info_arry = (CFArrayRef) CFDictionaryGetValue (bat_root_dict,
251                                 CFSTR ("IOBatteryInfo"));
252                 if (bat_info_arry == NULL)
253                 {
254                         CFRelease (bat_root_dict);
255                         continue;
256                 }
257                 bat_info_arry_len = CFArrayGetCount (bat_info_arry);
258
259                 for (bat_info_arry_pos = 0;
260                                 bat_info_arry_pos < bat_info_arry_len;
261                                 bat_info_arry_pos++)
262                 {
263                         bat_info_dict = (CFDictionaryRef) CFArrayGetValueAtIndex (bat_info_arry, bat_info_arry_pos);
264
265                         if (*ret_charge == INVALID_VALUE)
266                         {
267                                 temp_double = dict_get_double (bat_info_dict,
268                                                 "Capacity");
269                                 if (temp_double != INVALID_VALUE)
270                                         *ret_charge = temp_double / 1000.0;
271                         }
272
273                         if (*ret_current == INVALID_VALUE)
274                         {
275                                 temp_double = dict_get_double (bat_info_dict,
276                                                 "Current");
277                                 if (temp_double != INVALID_VALUE)
278                                         *ret_current = temp_double / 1000.0;
279                         }
280
281                         if (*ret_voltage == INVALID_VALUE)
282                         {
283                                 temp_double = dict_get_double (bat_info_dict,
284                                                 "Voltage");
285                                 if (temp_double != INVALID_VALUE)
286                                         *ret_voltage = temp_double / 1000.0;
287                         }
288                 }
289                 
290                 CFRelease (bat_root_dict);
291         }
292
293         IOObjectRelease (iterator);
294 } /* }}} void get_via_generic_iokit */
295 # endif /* HAVE_IOKIT_IOKITLIB_H */
296
297 static int battery_read (void) /* {{{ */
298 {
299         double charge  = INVALID_VALUE; /* Current charge in Ah */
300         double current = INVALID_VALUE; /* Current in A */
301         double voltage = INVALID_VALUE; /* Voltage in V */
302
303         double charge_rel = INVALID_VALUE; /* Current charge in percent */
304         double charge_abs = INVALID_VALUE; /* Total capacity */
305
306 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
307         get_via_io_power_sources (&charge_rel, &current, &voltage);
308 #endif
309 #if HAVE_IOKIT_IOKITLIB_H
310         get_via_generic_iokit (&charge_abs, &current, &voltage);
311 #endif
312
313         if ((charge_rel != INVALID_VALUE) && (charge_abs != INVALID_VALUE))
314                 charge = charge_abs * charge_rel / 100.0;
315
316         if (charge != INVALID_VALUE)
317                 battery_submit ("0", "charge", charge);
318         if (current != INVALID_VALUE)
319                 battery_submit ("0", "current", current);
320         if (voltage != INVALID_VALUE)
321                 battery_submit ("0", "voltage", voltage);
322 } /* }}} int battery_read */
323 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
324
325 #elif KERNEL_LINUX
326 /* Reads a file which contains only a number (and optionally a trailing
327  * newline) and parses that number. */
328 static int sysfs_file_to_buffer(char const *dir, /* {{{ */
329                 char const *power_supply,
330                 char const *basename,
331                 char *buffer, size_t buffer_size)
332 {
333         int status;
334         FILE *fp;
335         char filename[PATH_MAX];
336
337         ssnprintf (filename, sizeof (filename), "%s/%s/%s",
338                         dir, power_supply, basename);
339
340         /* No file isn't the end of the world -- not every system will be
341          * reporting the same set of statistics */
342         if (access (filename, R_OK) != 0)
343                 return ENOENT;
344
345         fp = fopen (filename, "r");
346         if (fp == NULL)
347         {
348                 status = errno;
349                 if (status != ENOENT)
350                 {
351                         char errbuf[1024];
352                         WARNING ("battery plugin: fopen (%s) failed: %s", filename,
353                                         sstrerror (status, errbuf, sizeof (errbuf)));
354                 }
355                 return status;
356         }
357
358         if (fgets (buffer, buffer_size, fp) == NULL)
359         {
360                 char errbuf[1024];
361                 status = errno;
362                 WARNING ("battery plugin: fgets failed: %s",
363                                 sstrerror (status, errbuf, sizeof (errbuf)));
364                 fclose (fp);
365                 return status;
366         }
367
368         strstripnewline (buffer);
369
370         fclose (fp);
371         return 0;
372 } /* }}} int sysfs_file_to_buffer */
373
374 /* Reads a file which contains only a number (and optionally a trailing
375  * newline) and parses that number. */
376 static int sysfs_file_to_gauge(char const *dir, /* {{{ */
377                 char const *power_supply,
378                 char const *basename, gauge_t *ret_value)
379 {
380         int status;
381         char buffer[32] = "";
382
383         status = sysfs_file_to_buffer (dir, power_supply, basename, buffer, sizeof (buffer));
384         if (status != 0)
385                 return (status);
386
387         return (strtogauge (buffer, ret_value));
388 } /* }}} sysfs_file_to_gauge */
389
390 static int read_sysfs_callback (char const *dir, /* {{{ */
391                 char const *power_supply,
392                 void *user_data)
393 {
394         int *battery_index = user_data;
395
396         char const *plugin_instance;
397         char buffer[32];
398         gauge_t v = NAN;
399         _Bool discharging = 0;
400         int status;
401
402         /* Ignore non-battery directories, such as AC power. */
403         status = sysfs_file_to_buffer (dir, power_supply, "type", buffer, sizeof (buffer));
404         if (status != 0)
405                 return (0);
406         if (strcasecmp ("Battery", buffer) != 0)
407                 return (0);
408
409         (void) sysfs_file_to_buffer (dir, power_supply, "status", buffer, sizeof (buffer));
410         if (strcasecmp ("Discharging", buffer) == 0)
411                 discharging = 1;
412
413         /* FIXME: This is a dirty hack for backwards compatibility: The battery
414          * plugin, for a very long time, has had the plugin_instance
415          * hard-coded to "0". So, to keep backwards compatibility, we'll use
416          * "0" for the first battery we find and the power_supply name for all
417          * following. This should be reverted in a future major version. */
418         plugin_instance = (*battery_index == 0) ? "0" : power_supply;
419         (*battery_index)++;
420
421         if (sysfs_file_to_gauge (dir, power_supply, "energy_now", &v) == 0)
422                 battery_submit (plugin_instance, "charge", v / 1000000.0);
423         if (sysfs_file_to_gauge (dir, power_supply, "power_now", &v) == 0)
424         {
425                 if (discharging)
426                         battery_submit (plugin_instance, "power", v / -1000000.0);
427                 else
428                         battery_submit (plugin_instance, "power", v / 1000000.0);
429         }
430         if (sysfs_file_to_gauge (dir, power_supply, "voltage_now", &v) == 0)
431                 battery_submit (plugin_instance, "voltage", v / 1000000.0);
432 #if 0
433         if (sysfs_file_to_gauge (dir, power_supply, "energy_full_design", &v) == 0)
434                 battery_submit (plugin_instance, "charge", v / 1000000.0);
435         if (sysfs_file_to_gauge (dir, power_supply, "energy_full", &v) == 0)
436                 battery_submit (plugin_instance, "charge", v / 1000000.0);
437         if (sysfs_file_to_gauge (dir, power_supply, "voltage_min_design", &v) == 0)
438                 battery_submit (plugin_instance, "voltage", v / 1000000.0);
439 #endif
440
441         return (0);
442 } /* }}} int read_sysfs_callback */
443
444 static int read_sysfs (void) /* {{{ */
445 {
446         int status;
447         int battery_counter = 0;
448
449         if (access (SYSFS_PATH, R_OK) != 0)
450                 return (ENOENT);
451
452         status = walk_directory (SYSFS_PATH, read_sysfs_callback,
453                         /* user_data = */ &battery_counter,
454                         /* include hidden */ 0);
455         return (status);
456 } /* }}} int read_sysfs */
457
458 static int read_acpi_callback (char const *dir, /* {{{ */
459                 char const *power_supply,
460                 void *user_data)
461 {
462         int *battery_index = user_data;
463
464         gauge_t current = NAN;
465         gauge_t voltage = NAN;
466         gauge_t charge  = NAN;
467         _Bool charging = 0;
468
469         char const *plugin_instance;
470         char filename[PATH_MAX];
471         char buffer[1024];
472
473         FILE *fh;
474
475         ssnprintf (filename, sizeof (filename), "%s/%s/state", dir, power_supply);
476         fh = fopen (filename, "r");
477         if ((fh = fopen (filename, "r")) == NULL)
478         {
479                 if ((errno == EAGAIN) || (errno == EINTR) || (errno == ENOENT))
480                         return (0);
481                 else
482                         return (errno);
483         }
484
485         /*
486          * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
487          * [11:00] <@tokkee> present:                 yes
488          * [11:00] <@tokkee> capacity state:          ok
489          * [11:00] <@tokkee> charging state:          charging
490          * [11:00] <@tokkee> present rate:            1724 mA
491          * [11:00] <@tokkee> remaining capacity:      4136 mAh
492          * [11:00] <@tokkee> present voltage:         12428 mV
493          */
494         while (fgets (buffer, sizeof (buffer), fh) != NULL)
495         {
496                 char *fields[8];
497                 int numfields;
498
499                 numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
500                 if (numfields < 3)
501                         continue;
502
503                 if ((strcmp (fields[0], "charging") == 0)
504                                 && (strcmp (fields[1], "state:") == 0))
505                 {
506                         if (strcmp (fields[2], "charging") == 0)
507                                 charging = 1;
508                         else
509                                 charging = 0;
510                         continue;
511                 }
512
513                 /* FIXME: The unit of "present rate" depends on the battery.
514                  * Modern batteries export watts, not amperes, i.e. it's not a
515                  * current anymore. We should check if the fourth column
516                  * contains "mA" and only use a current then. Otherwise, export
517                  * a power. */
518                 if ((strcmp (fields[0], "present") == 0)
519                                 && (strcmp (fields[1], "rate:") == 0))
520                         strtogauge (fields[2], &current);
521                 else if ((strcmp (fields[0], "remaining") == 0)
522                                 && (strcmp (fields[1], "capacity:") == 0))
523                         strtogauge (fields[2], &charge);
524                 else if ((strcmp (fields[0], "present") == 0)
525                                 && (strcmp (fields[1], "voltage:") == 0))
526                         strtogauge (fields[2], &voltage);
527         } /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */
528
529         fclose (fh);
530
531         if (!charging)
532                 current *= -1.0;
533
534         /* FIXME: This is a dirty hack for backwards compatibility: The battery
535          * plugin, for a very long time, has had the plugin_instance
536          * hard-coded to "0". So, to keep backwards compatibility, we'll use
537          * "0" for the first battery we find and the power_supply name for all
538          * following. This should be reverted in a future major version. */
539         plugin_instance = (*battery_index == 0) ? "0" : power_supply;
540         (*battery_index)++;
541
542         battery_submit (plugin_instance, "charge", charge / 1000.0);
543         battery_submit (plugin_instance, "current", current / 1000.0);
544         battery_submit (plugin_instance, "voltage", voltage / 1000.0);
545
546         return 0;
547 } /* }}} int read_acpi_callback */
548
549 static int read_acpi (void) /* {{{ */
550 {
551         int status;
552         int battery_counter = 0;
553
554         if (access (PROC_ACPI_PATH, R_OK) != 0)
555                 return (ENOENT);
556
557         status = walk_directory (PROC_ACPI_PATH, read_acpi_callback,
558                         /* user_data = */ &battery_counter,
559                         /* include hidden */ 0);
560         return (status);
561 } /* }}} int read_acpi */
562
563 static int read_pmu (void) /* {{{ */
564 {
565         int i;
566
567         /* The upper limit here is just a safeguard. If there is a system with
568          * more than 100 batteries, this can easily be increased. */
569         for (i = 0; i < 100; i++)
570         {
571                 FILE *fh;
572
573                 char buffer[1024];
574                 char filename[PATH_MAX];
575                 char plugin_instance[DATA_MAX_NAME_LEN];
576
577                 gauge_t current = NAN;
578                 gauge_t voltage = NAN;
579                 gauge_t charge  = NAN;
580
581                 ssnprintf (filename, sizeof (filename), PROC_PMU_PATH_FORMAT, i);
582                 if (access (filename, R_OK) != 0)
583                         break;
584
585                 ssnprintf (plugin_instance, sizeof (plugin_instance), "%i", i);
586
587                 fh = fopen (filename, "r");
588                 if (fh == NULL)
589                 {
590                         if (errno == ENOENT)
591                                 break;
592                         else if ((errno == EAGAIN) || (errno == EINTR))
593                                 continue;
594                         else
595                                 return (errno);
596                 }
597
598                 while (fgets (buffer, sizeof (buffer), fh) != NULL)
599                 {
600                         char *fields[8];
601                         int numfields;
602
603                         numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
604                         if (numfields < 3)
605                                 continue;
606
607                         if (strcmp ("current", fields[0]) == 0)
608                                 strtogauge (fields[2], &current);
609                         else if (strcmp ("voltage", fields[0]) == 0)
610                                 strtogauge (fields[2], &voltage);
611                         else if (strcmp ("charge", fields[0]) == 0)
612                                 strtogauge (fields[2], &charge);
613                 }
614
615                 fclose (fh);
616                 fh = NULL;
617
618                 battery_submit (plugin_instance, "charge", charge / 1000.0);
619                 battery_submit (plugin_instance, "current", current / 1000.0);
620                 battery_submit (plugin_instance, "voltage", voltage / 1000.0);
621         }
622
623         if (i == 0)
624                 return (ENOENT);
625         return (0);
626 } /* }}} int read_pmu */
627
628 static int battery_read (void) /* {{{ */
629 {
630         int status;
631
632         DEBUG ("battery plugin: Trying sysfs ...");
633         status = read_sysfs ();
634         if (status == 0)
635                 return (0);
636
637         DEBUG ("battery plugin: Trying acpi ...");
638         status = read_acpi ();
639         if (status == 0)
640                 return (0);
641
642         DEBUG ("battery plugin: Trying pmu ...");
643         status = read_pmu ();
644         if (status == 0)
645                 return (0);
646
647         ERROR ("battery plugin: Add available input methods failed.");
648         return (-1);
649 } /* }}} int battery_read */
650 #endif /* KERNEL_LINUX */
651
652 void module_register (void)
653 {
654         plugin_register_read ("battery", battery_read);
655 } /* void module_register */