First version of a (hopefully) working battery plugin for darwin.
[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 double dict_get_double (CFDictionaryRef dict, char *key_string)
206 {
207         double      val_double;
208         long long   val_int;
209         CFNumberRef val_obj;
210         CFStringRef key_obj;
211
212         key_obj = CFStringCreateWithCString (kCFAllocatorDefault, key_string,
213                         kCFStringEncodingASCII);
214         if (key_obj == NULL)
215         {
216                 DBG ("CFStringCreateWithCString (%s) failed.\n", key_string);
217                 return (INVALID_VALUE);
218         }
219
220         if ((val_obj = CFDictionaryGetValue (dict, key_obj)) == NULL)
221         {
222                 DBG ("CFDictionaryGetValue (%s) failed.\n", key_string);
223                 CFRelease (key_obj);
224                 return (INVALID_VALUE);
225         }
226         CFRelease (key_obj);
227
228         if (CFGetTypeID (val_obj) == CFNumberGetTypeID ())
229         {
230                 if (CFNumberIsFloatType (val_obj))
231                 {
232                         CFNumberGetValue (val_obj,
233                                         kCFNumberDoubleType,
234                                         &val_double);
235                 }
236                 else
237                 {
238                         CFNumberGetValue (val_obj,
239                                         kCFNumberLongLongType,
240                                         &val_int);
241                         val_double = val_int;
242                 }
243         }
244         else
245         {
246                 DBG ("CFGetTypeID (val_obj) = %i\n", (int) CFGetTypeID (val_obj));
247                 return (INVALID_VALUE);
248         }
249
250         return (val_double);
251 }
252
253 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
254 static void get_via_io_power_sources (double *ret_charge,
255                 double *ret_current,
256                 double *ret_voltage)
257 {
258         CFTypeRef       ps_raw;
259         CFArrayRef      ps_array;
260         int             ps_array_len;
261         CFDictionaryRef ps_dict;
262         CFTypeRef       ps_obj;
263
264         double temp_double;
265         int i;
266
267         ps_raw       = IOPSCopyPowerSourcesInfo ();
268         ps_array     = IOPSCopyPowerSourcesList (ps_raw);
269         ps_array_len = CFArrayGetCount (ps_array);
270
271         DBG ("ps_array_len == %i", ps_array_len);
272
273         for (i = 0; i < ps_array_len; i++)
274         {
275                 ps_obj  = CFArrayGetValueAtIndex (ps_array, i);
276                 ps_dict = IOPSGetPowerSourceDescription (ps_raw, ps_obj);
277
278                 if (ps_dict == NULL)
279                 {
280                         DBG ("IOPSGetPowerSourceDescription failed.");
281                         continue;
282                 }
283
284                 if (CFGetTypeID (ps_dict) != CFDictionaryGetTypeID ())
285                 {
286                         DBG ("IOPSGetPowerSourceDescription did not return a CFDictionaryRef");
287                         continue;
288                 }
289
290                 /* FIXME: Check if this is really an internal battery */
291
292                 if (*ret_charge == INVALID_VALUE)
293                 {
294                         double current_charge;
295                         double total_charge;
296
297                         current_charge = dict_get_double (ps_dict,
298                                         kIOPSCurrentCapacityKey);
299                         total_charge = dict_get_double (ps_dict,
300                                         kIOPSMaxCapacityKey);
301
302                         if ((current_charge != INVALID_VALUE)
303                                         && (total_charge != INVALID_VALUE)
304                                         && (current_charge >= 0.0)
305                                         && (current_charge <= 100.0))
306                                 *ret_charge = total_charge * current_charge / 100.0;
307                 }
308
309                 if (*ret_current == INVALID_VALUE)
310                 {
311                         temp_double = dict_get_double (ps_dict,
312                                         kIOPSCurrentKey);
313                         if (temp_double != INVALID_VALUE)
314                                 *ret_current = temp_double / 1000.0;
315                 }
316
317                 if (*ret_voltage == INVALID_VALUE)
318                 {
319                         temp_double = dict_get_double (ps_dict,
320                                         kIOPSVoltageKey);
321                         if (temp_double != INVALID_VALUE)
322                                 *ret_voltage = temp_double / 1000.0;
323                 }
324         }
325
326         CFRelease(ps_array);
327         CFRelease(ps_raw);
328 }
329 #endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
330
331 #if HAVE_IOKIT_IOKITLIB_H
332 static void get_via_generic_iokit (double *ret_charge,
333                 double *ret_current,
334                 double *ret_voltage)
335 {
336         kern_return_t   status;
337         io_iterator_t   iterator;
338         io_object_t     io_obj;
339
340         CFDictionaryRef bat_root_dict;
341         CFArrayRef      bat_info_arry;
342         CFIndex         bat_info_arry_len;
343         CFIndex         bat_info_arry_pos;
344         CFDictionaryRef bat_info_dict;
345
346         double temp_double;
347
348         status = IOServiceGetMatchingServices (kIOMasterPortDefault,
349                         IOServiceNameMatching ("battery"),
350                         &iterator);
351         if (status != kIOReturnSuccess)
352         {
353                 DBG ("IOServiceGetMatchingServices failed.");
354                 return;
355         }
356
357         while ((io_obj = IOIteratorNext (iterator)))
358         {
359                 status = IORegistryEntryCreateCFProperties (io_obj,
360                                 (CFMutableDictionaryRef *) &bat_root_dict,
361                                 kCFAllocatorDefault,
362                                 kNilOptions);
363                 if (status != kIOReturnSuccess)
364                 {
365                         DBG ("IORegistryEntryCreateCFProperties failed.");
366                         continue;
367                 }
368
369                 bat_info_arry = (CFArrayRef) CFDictionaryGetValue (bat_root_dict,
370                                 CFSTR ("IOBatteryInfo"));
371                 if (bat_info_arry == NULL)
372                 {
373                         CFRelease (bat_root_dict);
374                         continue;
375                 }
376                 bat_info_arry_len = CFArrayGetCount (bat_info_arry);
377
378                 for (bat_info_arry_pos = 0;
379                                 bat_info_arry_pos < bat_info_arry_len;
380                                 bat_info_arry_pos++)
381                 {
382                         bat_info_dict = (CFDictionaryRef) CFArrayGetValueAtIndex (bat_info_arry, bat_info_arry_pos);
383
384                         if (*ret_current == INVALID_VALUE)
385                         {
386                                 temp_double = dict_get_double (bat_info_dict,
387                                                 "Current");
388                                 if (temp_double != INVALID_VALUE)
389                                         *ret_current = temp_double / 1000.0;
390                         }
391
392                         if (*ret_voltage == INVALID_VALUE)
393                         {
394                                 temp_double = dict_get_double (bat_info_dict,
395                                                 "Voltage");
396                                 if (temp_double != INVALID_VALUE)
397                                         *ret_voltage = temp_double / 1000.0;
398                         }
399                 }
400                 
401                 CFRelease (bat_root_dict);
402         }
403
404         IOObjectRelease (iterator);
405 }
406 #endif /* HAVE_IOKIT_IOKITLIB_H */
407
408 static void battery_read (void)
409 {
410 #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
411         double charge  = INVALID_VALUE;
412         double current = INVALID_VALUE;
413         double voltage = INVALID_VALUE;
414
415 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
416         get_via_io_power_sources (&charge, &current, &voltage);
417 #endif
418
419 #if HAVE_IOKIT_IOKITLIB_H
420         if ((charge == INVALID_VALUE)
421                         || (current == INVALID_VALUE)
422                         || (voltage == INVALID_VALUE))
423                 get_via_generic_iokit (&charge, &current, &voltage);
424 #endif
425
426         if ((charge != INVALID_VALUE)
427                         || (current != INVALID_VALUE)
428                         || (voltage != INVALID_VALUE))
429                 battery_submit ("battery", current, voltage, charge);
430 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
431
432 #elif KERNEL_LINUX
433         FILE *fh;
434         char buffer[BUFSIZE];
435         char filename[BUFSIZE];
436         
437         char *fields[8];
438         int numfields;
439
440         int i;
441         int len;
442
443         for (i = 0; i < battery_pmu_num; i++)
444         {
445                 char    batnum_str[BUFSIZE];
446                 double  current = INVALID_VALUE;
447                 double  voltage = INVALID_VALUE;
448                 double  charge  = INVALID_VALUE;
449                 double *valptr = NULL;
450
451                 len = snprintf (filename, BUFSIZE, battery_pmu_file, i);
452                 if ((len >= BUFSIZE) || (len < 0))
453                         continue;
454
455                 len = snprintf (batnum_str, BUFSIZE, "%i", i);
456                 if ((len >= BUFSIZE) || (len < 0))
457                         continue;
458
459                 if ((fh = fopen (filename, "r")) == NULL)
460                         continue;
461
462                 while (fgets (buffer, BUFSIZE, fh) != NULL)
463                 {
464                         numfields = strsplit (buffer, fields, 8);
465
466                         if (numfields < 3)
467                                 continue;
468
469                         if (strcmp ("current", fields[0]) == 0)
470                                 valptr = &current;
471                         else if (strcmp ("voltage", fields[0]) == 0)
472                                 valptr = &voltage;
473                         else if (strcmp ("charge", fields[0]) == 0)
474                                 valptr = &charge;
475                         else
476                                 valptr = NULL;
477
478                         if (valptr != NULL)
479                         {
480                                 char *endptr;
481
482                                 endptr = NULL;
483                                 errno  = 0;
484
485                                 *valptr = strtod (fields[2], &endptr) / 1000.0;
486
487                                 if ((fields[2] == endptr) || (errno != 0))
488                                         *valptr = INVALID_VALUE;
489                         }
490                 }
491
492                 if ((current != INVALID_VALUE)
493                                 || (voltage != INVALID_VALUE)
494                                 || (charge  != INVALID_VALUE))
495                         battery_submit (batnum_str, current, voltage, charge);
496
497                 fclose (fh);
498                 fh = NULL;
499         }
500
501         if (access ("/proc/acpi/battery", R_OK | X_OK) == 0)
502         {
503                 double  current = INVALID_VALUE;
504                 double  voltage = INVALID_VALUE;
505                 double  charge  = INVALID_VALUE;
506                 double *valptr = NULL;
507                 int charging = 0;
508
509                 struct dirent *ent;
510                 DIR *dh;
511
512                 if ((dh = opendir ("/proc/acpi/battery")) == NULL)
513                 {
514                         syslog (LOG_ERR, "Cannot open `/proc/acpi/battery': %s", strerror (errno));
515                         return;
516                 }
517
518                 while ((ent = readdir (dh)) != NULL)
519                 {
520                         if (ent->d_name[0] == '.')
521                                 continue;
522
523                         len = snprintf (filename, BUFSIZE, "/proc/acpi/battery/%s/state", ent->d_name);
524                         if ((len >= BUFSIZE) || (len < 0))
525                                 continue;
526
527                         if ((fh = fopen (filename, "r")) == NULL)
528                         {
529                                 syslog (LOG_ERR, "Cannot open `%s': %s", filename, strerror (errno));
530                                 continue;
531                         }
532
533                         /*
534                          * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
535                          * [11:00] <@tokkee> present:                 yes
536                          * [11:00] <@tokkee> capacity state:          ok
537                          * [11:00] <@tokkee> charging state:          charging
538                          * [11:00] <@tokkee> present rate:            1724 mA
539                          * [11:00] <@tokkee> remaining capacity:      4136 mAh
540                          * [11:00] <@tokkee> present voltage:         12428 mV
541                          */
542                         while (fgets (buffer, BUFSIZE, fh) != NULL)
543                         {
544                                 numfields = strsplit (buffer, fields, 8);
545
546                                 if (numfields < 3)
547                                         continue;
548
549                                 if ((strcmp (fields[0], "present") == 0)
550                                                 && (strcmp (fields[1], "rate:") == 0))
551                                         valptr = &current;
552                                 else if ((strcmp (fields[0], "remaining") == 0)
553                                                 && (strcmp (fields[1], "capacity:") == 0))
554                                         valptr = &charge;
555                                 else if ((strcmp (fields[0], "present") == 0)
556                                                 && (strcmp (fields[1], "voltage:") == 0))
557                                         valptr = &voltage;
558                                 else
559                                         valptr = NULL;
560
561                                 if ((strcmp (fields[0], "charging") == 0)
562                                                 && (strcmp (fields[1], "state:") == 0))
563                                 {
564                                         if (strcmp (fields[2], "charging") == 0)
565                                                 charging = 1;
566                                         else
567                                                 charging = 0;
568                                 }
569
570                                 if (valptr != NULL)
571                                 {
572                                         char *endptr;
573
574                                         endptr = NULL;
575                                         errno  = 0;
576
577                                         *valptr = strtod (fields[2], &endptr) / 1000.0;
578
579                                         if ((fields[2] == endptr) || (errno != 0))
580                                                 *valptr = INVALID_VALUE;
581                                 }
582                         }
583
584                         if ((current != INVALID_VALUE) && (charging == 0))
585                                         current *= -1;
586
587                         if ((current != INVALID_VALUE)
588                                         || (voltage != INVALID_VALUE)
589                                         || (charge  != INVALID_VALUE))
590                                 battery_submit (ent->d_name, current, voltage, charge);
591
592                         fclose (fh);
593                 }
594
595                 closedir (dh);
596         }
597 #endif /* KERNEL_LINUX */
598 }
599 #else
600 # define battery_read NULL
601 #endif /* BATTERY_HAVE_READ */
602
603 void module_register (void)
604 {
605         plugin_register (MODULE_NAME, battery_init, battery_read, NULL);
606         plugin_register ("battery_current", NULL, NULL, battery_current_write);
607         plugin_register ("battery_voltage", NULL, NULL, battery_voltage_write);
608         plugin_register ("battery_charge",  NULL, NULL, battery_charge_write);
609 }
610
611 #undef BUFSIZE
612 #undef MODULE_NAME