apcups plugin: Warn about the irritating name `apcups_charge_pct'.
[collectd.git] / src / battery.c
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  **/
22
23 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_debug.h"
27
28 #define MODULE_NAME "battery"
29 #define BUFSIZE 512
30
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
55
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
61
62 #define INVALID_VALUE 47841.29
63
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";
67
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;
74
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;
81
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;
88
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 */
92
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 */
97
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 */
103
104 #elif KERNEL_LINUX
105         int len;
106         char filename[BUFSIZE];
107
108         for (battery_pmu_num = 0; ; battery_pmu_num++)
109         {
110                 len = snprintf (filename, BUFSIZE, battery_pmu_file, battery_pmu_num);
111
112                 if ((len >= BUFSIZE) || (len < 0))
113                         break;
114
115                 if (access (filename, R_OK))
116                         break;
117         }
118 #endif /* KERNEL_LINUX */
119
120         return;
121 }
122
123 static void battery_current_write (char *host, char *inst, char *val)
124 {
125         char filename[BUFSIZE];
126         int len;
127
128         len = snprintf (filename, BUFSIZE, battery_current_file, inst);
129         if ((len >= BUFSIZE) || (len < 0))
130                 return;
131
132         rrd_update_file (host, filename, val,
133                         ds_def_current, ds_num_current);
134 }
135
136 static void battery_voltage_write (char *host, char *inst, char *val)
137 {
138         char filename[BUFSIZE];
139         int len;
140
141         len = snprintf (filename, BUFSIZE, battery_voltage_file, inst);
142         if ((len >= BUFSIZE) || (len < 0))
143                 return;
144
145         rrd_update_file (host, filename, val,
146                         ds_def_voltage, ds_num_voltage);
147 }
148
149 static void battery_charge_write (char *host, char *inst, char *val)
150 {
151         char filename[BUFSIZE];
152         int len;
153
154         len = snprintf (filename, BUFSIZE, battery_charge_file, inst);
155         if ((len >= BUFSIZE) || (len < 0))
156                 return;
157
158         rrd_update_file (host, filename, val,
159                         ds_def_charge, ds_num_charge);
160 }
161
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];
167
168         if (current != INVALID_VALUE)
169         {
170                 len = snprintf (buffer, BUFSIZE, "N:%.3f", current);
171
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         }
179
180         if (voltage != INVALID_VALUE)
181         {
182                 len = snprintf (buffer, BUFSIZE, "N:%.3f", voltage);
183
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         }
191
192         if (charge != INVALID_VALUE)
193         {
194                 len = snprintf (buffer, BUFSIZE, "N:%.3f", charge);
195
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 }
204
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;
212
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         }
220
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);
228
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         }
250
251         return (val_double);
252 }
253 #endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H */
254
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;
265
266         double temp_double;
267         int i;
268
269         ps_raw       = IOPSCopyPowerSourcesInfo ();
270         ps_array     = IOPSCopyPowerSourcesList (ps_raw);
271         ps_array_len = CFArrayGetCount (ps_array);
272
273         DBG ("ps_array_len == %i", ps_array_len);
274
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);
279
280                 if (ps_dict == NULL)
281                 {
282                         DBG ("IOPSGetPowerSourceDescription failed.");
283                         continue;
284                 }
285
286                 if (CFGetTypeID (ps_dict) != CFDictionaryGetTypeID ())
287                 {
288                         DBG ("IOPSGetPowerSourceDescription did not return a CFDictionaryRef");
289                         continue;
290                 }
291
292                 /* FIXME: Check if this is really an internal battery */
293
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                 }
304
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                 }
312
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         }
321
322         CFRelease(ps_array);
323         CFRelease(ps_raw);
324 }
325 #endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
326
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;
335
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;
341
342         double temp_double;
343
344         status = IOServiceGetMatchingServices (kIOMasterPortDefault,
345                         IOServiceNameMatching ("battery"),
346                         &iterator);
347         if (status != kIOReturnSuccess)
348         {
349                 DBG ("IOServiceGetMatchingServices failed.");
350                 return;
351         }
352
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                 }
364
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);
373
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);
379
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                         }
387
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                         }
395
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                 }
404                 
405                 CFRelease (bat_root_dict);
406         }
407
408         IOObjectRelease (iterator);
409 }
410 #endif /* HAVE_IOKIT_IOKITLIB_H */
411
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 */
418
419         double charge_rel = INVALID_VALUE; /* Current charge in percent */
420         double charge_abs = INVALID_VALUE; /* Total capacity */
421
422 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
423         get_via_io_power_sources (&charge_rel, &current, &voltage);
424 #endif
425 #if HAVE_IOKIT_IOKITLIB_H
426         get_via_generic_iokit (&charge_abs, &current, &voltage);
427 #endif
428
429         if ((charge_rel != INVALID_VALUE) && (charge_abs != INVALID_VALUE))
430                 charge = charge_abs * charge_rel / 100.0;
431
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 */
437
438 #elif KERNEL_LINUX
439         FILE *fh;
440         char buffer[BUFSIZE];
441         char filename[BUFSIZE];
442         
443         char *fields[8];
444         int numfields;
445
446         int i;
447         int len;
448
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;
456
457                 len = snprintf (filename, BUFSIZE, battery_pmu_file, i);
458                 if ((len >= BUFSIZE) || (len < 0))
459                         continue;
460
461                 len = snprintf (batnum_str, BUFSIZE, "%i", i);
462                 if ((len >= BUFSIZE) || (len < 0))
463                         continue;
464
465                 if ((fh = fopen (filename, "r")) == NULL)
466                         continue;
467
468                 while (fgets (buffer, BUFSIZE, fh) != NULL)
469                 {
470                         numfields = strsplit (buffer, fields, 8);
471
472                         if (numfields < 3)
473                                 continue;
474
475                         if (strcmp ("current", fields[0]) == 0)
476                                 valptr = &current;
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;
483
484                         if (valptr != NULL)
485                         {
486                                 char *endptr;
487
488                                 endptr = NULL;
489                                 errno  = 0;
490
491                                 *valptr = strtod (fields[2], &endptr) / 1000.0;
492
493                                 if ((fields[2] == endptr) || (errno != 0))
494                                         *valptr = INVALID_VALUE;
495                         }
496                 }
497
498                 if ((current != INVALID_VALUE)
499                                 || (voltage != INVALID_VALUE)
500                                 || (charge  != INVALID_VALUE))
501                         battery_submit (batnum_str, current, voltage, charge);
502
503                 fclose (fh);
504                 fh = NULL;
505         }
506
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;
514
515                 struct dirent *ent;
516                 DIR *dh;
517
518                 if ((dh = opendir ("/proc/acpi/battery")) == NULL)
519                 {
520                         syslog (LOG_ERR, "Cannot open `/proc/acpi/battery': %s", strerror (errno));
521                         return;
522                 }
523
524                 while ((ent = readdir (dh)) != NULL)
525                 {
526                         if (ent->d_name[0] == '.')
527                                 continue;
528
529                         len = snprintf (filename, BUFSIZE, "/proc/acpi/battery/%s/state", ent->d_name);
530                         if ((len >= BUFSIZE) || (len < 0))
531                                 continue;
532
533                         if ((fh = fopen (filename, "r")) == NULL)
534                         {
535                                 syslog (LOG_ERR, "Cannot open `%s': %s", filename, strerror (errno));
536                                 continue;
537                         }
538
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);
551
552                                 if (numfields < 3)
553                                         continue;
554
555                                 if ((strcmp (fields[0], "present") == 0)
556                                                 && (strcmp (fields[1], "rate:") == 0))
557                                         valptr = &current;
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;
566
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                                 }
575
576                                 if (valptr != NULL)
577                                 {
578                                         char *endptr;
579
580                                         endptr = NULL;
581                                         errno  = 0;
582
583                                         *valptr = strtod (fields[2], &endptr) / 1000.0;
584
585                                         if ((fields[2] == endptr) || (errno != 0))
586                                                 *valptr = INVALID_VALUE;
587                                 }
588                         }
589
590                         if ((current != INVALID_VALUE) && (charging == 0))
591                                         current *= -1;
592
593                         if ((current != INVALID_VALUE)
594                                         || (voltage != INVALID_VALUE)
595                                         || (charge  != INVALID_VALUE))
596                                 battery_submit (ent->d_name, current, voltage, charge);
597
598                         fclose (fh);
599                 }
600
601                 closedir (dh);
602         }
603 #endif /* KERNEL_LINUX */
604 }
605 #else
606 # define battery_read NULL
607 #endif /* BATTERY_HAVE_READ */
608
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 }
616
617 #undef BUFSIZE
618 #undef MODULE_NAME