solaris-fixes branch: Applied the swap-patch by Christophe Kalt.
[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.", 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", (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                         /* This is the charge in percent. */
295                         temp_double = dict_get_double (ps_dict,
296                                         kIOPSCurrentCapacityKey);
297                         if ((temp_double != INVALID_VALUE)
298                                         && (temp_double >= 0.0)
299                                         && (temp_double <= 100.0))
300                                 *ret_charge = temp_double;
301                 }
302
303                 if (*ret_current == INVALID_VALUE)
304                 {
305                         temp_double = dict_get_double (ps_dict,
306                                         kIOPSCurrentKey);
307                         if (temp_double != INVALID_VALUE)
308                                 *ret_current = temp_double / 1000.0;
309                 }
310
311                 if (*ret_voltage == INVALID_VALUE)
312                 {
313                         temp_double = dict_get_double (ps_dict,
314                                         kIOPSVoltageKey);
315                         if (temp_double != INVALID_VALUE)
316                                 *ret_voltage = temp_double / 1000.0;
317                 }
318         }
319
320         CFRelease(ps_array);
321         CFRelease(ps_raw);
322 }
323 #endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
324
325 #if HAVE_IOKIT_IOKITLIB_H
326 static void get_via_generic_iokit (double *ret_charge,
327                 double *ret_current,
328                 double *ret_voltage)
329 {
330         kern_return_t   status;
331         io_iterator_t   iterator;
332         io_object_t     io_obj;
333
334         CFDictionaryRef bat_root_dict;
335         CFArrayRef      bat_info_arry;
336         CFIndex         bat_info_arry_len;
337         CFIndex         bat_info_arry_pos;
338         CFDictionaryRef bat_info_dict;
339
340         double temp_double;
341
342         status = IOServiceGetMatchingServices (kIOMasterPortDefault,
343                         IOServiceNameMatching ("battery"),
344                         &iterator);
345         if (status != kIOReturnSuccess)
346         {
347                 DBG ("IOServiceGetMatchingServices failed.");
348                 return;
349         }
350
351         while ((io_obj = IOIteratorNext (iterator)))
352         {
353                 status = IORegistryEntryCreateCFProperties (io_obj,
354                                 (CFMutableDictionaryRef *) &bat_root_dict,
355                                 kCFAllocatorDefault,
356                                 kNilOptions);
357                 if (status != kIOReturnSuccess)
358                 {
359                         DBG ("IORegistryEntryCreateCFProperties failed.");
360                         continue;
361                 }
362
363                 bat_info_arry = (CFArrayRef) CFDictionaryGetValue (bat_root_dict,
364                                 CFSTR ("IOBatteryInfo"));
365                 if (bat_info_arry == NULL)
366                 {
367                         CFRelease (bat_root_dict);
368                         continue;
369                 }
370                 bat_info_arry_len = CFArrayGetCount (bat_info_arry);
371
372                 for (bat_info_arry_pos = 0;
373                                 bat_info_arry_pos < bat_info_arry_len;
374                                 bat_info_arry_pos++)
375                 {
376                         bat_info_dict = (CFDictionaryRef) CFArrayGetValueAtIndex (bat_info_arry, bat_info_arry_pos);
377
378                         if (*ret_charge == INVALID_VALUE)
379                         {
380                                 temp_double = dict_get_double (bat_info_dict,
381                                                 "Capacity");
382                                 if (temp_double != INVALID_VALUE)
383                                         *ret_charge = temp_double / 1000.0;
384                         }
385
386                         if (*ret_current == INVALID_VALUE)
387                         {
388                                 temp_double = dict_get_double (bat_info_dict,
389                                                 "Current");
390                                 if (temp_double != INVALID_VALUE)
391                                         *ret_current = temp_double / 1000.0;
392                         }
393
394                         if (*ret_voltage == INVALID_VALUE)
395                         {
396                                 temp_double = dict_get_double (bat_info_dict,
397                                                 "Voltage");
398                                 if (temp_double != INVALID_VALUE)
399                                         *ret_voltage = temp_double / 1000.0;
400                         }
401                 }
402                 
403                 CFRelease (bat_root_dict);
404         }
405
406         IOObjectRelease (iterator);
407 }
408 #endif /* HAVE_IOKIT_IOKITLIB_H */
409
410 static void battery_read (void)
411 {
412 #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
413         double charge  = INVALID_VALUE; /* Current charge in Ah */
414         double current = INVALID_VALUE; /* Current in A */
415         double voltage = INVALID_VALUE; /* Voltage in V */
416
417         double charge_rel = INVALID_VALUE; /* Current charge in percent */
418         double charge_abs = INVALID_VALUE; /* Total capacity */
419
420 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
421         get_via_io_power_sources (&charge_rel, &current, &voltage);
422 #endif
423 #if HAVE_IOKIT_IOKITLIB_H
424         get_via_generic_iokit (&charge_abs, &current, &voltage);
425 #endif
426
427         if ((charge_rel != INVALID_VALUE) && (charge_abs != INVALID_VALUE))
428                 charge = charge_abs * charge_rel / 100.0;
429
430         if ((charge != INVALID_VALUE)
431                         || (current != INVALID_VALUE)
432                         || (voltage != INVALID_VALUE))
433                 battery_submit ("0", current, voltage, charge);
434 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
435
436 #elif KERNEL_LINUX
437         FILE *fh;
438         char buffer[BUFSIZE];
439         char filename[BUFSIZE];
440         
441         char *fields[8];
442         int numfields;
443
444         int i;
445         int len;
446
447         for (i = 0; i < battery_pmu_num; i++)
448         {
449                 char    batnum_str[BUFSIZE];
450                 double  current = INVALID_VALUE;
451                 double  voltage = INVALID_VALUE;
452                 double  charge  = INVALID_VALUE;
453                 double *valptr = NULL;
454
455                 len = snprintf (filename, BUFSIZE, battery_pmu_file, i);
456                 if ((len >= BUFSIZE) || (len < 0))
457                         continue;
458
459                 len = snprintf (batnum_str, BUFSIZE, "%i", i);
460                 if ((len >= BUFSIZE) || (len < 0))
461                         continue;
462
463                 if ((fh = fopen (filename, "r")) == NULL)
464                         continue;
465
466                 while (fgets (buffer, BUFSIZE, fh) != NULL)
467                 {
468                         numfields = strsplit (buffer, fields, 8);
469
470                         if (numfields < 3)
471                                 continue;
472
473                         if (strcmp ("current", fields[0]) == 0)
474                                 valptr = &current;
475                         else if (strcmp ("voltage", fields[0]) == 0)
476                                 valptr = &voltage;
477                         else if (strcmp ("charge", fields[0]) == 0)
478                                 valptr = &charge;
479                         else
480                                 valptr = NULL;
481
482                         if (valptr != NULL)
483                         {
484                                 char *endptr;
485
486                                 endptr = NULL;
487                                 errno  = 0;
488
489                                 *valptr = strtod (fields[2], &endptr) / 1000.0;
490
491                                 if ((fields[2] == endptr) || (errno != 0))
492                                         *valptr = INVALID_VALUE;
493                         }
494                 }
495
496                 if ((current != INVALID_VALUE)
497                                 || (voltage != INVALID_VALUE)
498                                 || (charge  != INVALID_VALUE))
499                         battery_submit (batnum_str, current, voltage, charge);
500
501                 fclose (fh);
502                 fh = NULL;
503         }
504
505         if (access ("/proc/acpi/battery", R_OK | X_OK) == 0)
506         {
507                 double  current = INVALID_VALUE;
508                 double  voltage = INVALID_VALUE;
509                 double  charge  = INVALID_VALUE;
510                 double *valptr = NULL;
511                 int charging = 0;
512
513                 struct dirent *ent;
514                 DIR *dh;
515
516                 if ((dh = opendir ("/proc/acpi/battery")) == NULL)
517                 {
518                         syslog (LOG_ERR, "Cannot open `/proc/acpi/battery': %s", strerror (errno));
519                         return;
520                 }
521
522                 while ((ent = readdir (dh)) != NULL)
523                 {
524                         if (ent->d_name[0] == '.')
525                                 continue;
526
527                         len = snprintf (filename, BUFSIZE, "/proc/acpi/battery/%s/state", ent->d_name);
528                         if ((len >= BUFSIZE) || (len < 0))
529                                 continue;
530
531                         if ((fh = fopen (filename, "r")) == NULL)
532                         {
533                                 syslog (LOG_ERR, "Cannot open `%s': %s", filename, strerror (errno));
534                                 continue;
535                         }
536
537                         /*
538                          * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
539                          * [11:00] <@tokkee> present:                 yes
540                          * [11:00] <@tokkee> capacity state:          ok
541                          * [11:00] <@tokkee> charging state:          charging
542                          * [11:00] <@tokkee> present rate:            1724 mA
543                          * [11:00] <@tokkee> remaining capacity:      4136 mAh
544                          * [11:00] <@tokkee> present voltage:         12428 mV
545                          */
546                         while (fgets (buffer, BUFSIZE, fh) != NULL)
547                         {
548                                 numfields = strsplit (buffer, fields, 8);
549
550                                 if (numfields < 3)
551                                         continue;
552
553                                 if ((strcmp (fields[0], "present") == 0)
554                                                 && (strcmp (fields[1], "rate:") == 0))
555                                         valptr = &current;
556                                 else if ((strcmp (fields[0], "remaining") == 0)
557                                                 && (strcmp (fields[1], "capacity:") == 0))
558                                         valptr = &charge;
559                                 else if ((strcmp (fields[0], "present") == 0)
560                                                 && (strcmp (fields[1], "voltage:") == 0))
561                                         valptr = &voltage;
562                                 else
563                                         valptr = NULL;
564
565                                 if ((strcmp (fields[0], "charging") == 0)
566                                                 && (strcmp (fields[1], "state:") == 0))
567                                 {
568                                         if (strcmp (fields[2], "charging") == 0)
569                                                 charging = 1;
570                                         else
571                                                 charging = 0;
572                                 }
573
574                                 if (valptr != NULL)
575                                 {
576                                         char *endptr;
577
578                                         endptr = NULL;
579                                         errno  = 0;
580
581                                         *valptr = strtod (fields[2], &endptr) / 1000.0;
582
583                                         if ((fields[2] == endptr) || (errno != 0))
584                                                 *valptr = INVALID_VALUE;
585                                 }
586                         }
587
588                         if ((current != INVALID_VALUE) && (charging == 0))
589                                         current *= -1;
590
591                         if ((current != INVALID_VALUE)
592                                         || (voltage != INVALID_VALUE)
593                                         || (charge  != INVALID_VALUE))
594                                 battery_submit (ent->d_name, current, voltage, charge);
595
596                         fclose (fh);
597                 }
598
599                 closedir (dh);
600         }
601 #endif /* KERNEL_LINUX */
602 }
603 #else
604 # define battery_read NULL
605 #endif /* BATTERY_HAVE_READ */
606
607 void module_register (void)
608 {
609         plugin_register (MODULE_NAME, battery_init, battery_read, NULL);
610         plugin_register ("battery_current", NULL, NULL, battery_current_write);
611         plugin_register ("battery_voltage", NULL, NULL, battery_voltage_write);
612         plugin_register ("battery_charge",  NULL, NULL, battery_charge_write);
613 }
614
615 #undef BUFSIZE
616 #undef MODULE_NAME