Merge branch 'v391-fix' into v392-fix
[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_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_PS_IOPOWERSOURCES_H
90         /* No global variables */
91 /* #endif 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_PS_IOPOWERSOURCES_H
101         /* No init neccessary */
102 /* #endif 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 static void battery_read (void)
206 {
207 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
208         CFTypeRef       ps_raw;
209         CFArrayRef      ps_array;
210         int             ps_array_len;
211         CFDictionaryRef ps_dict;
212         CFTypeRef       ps_obj;
213         CFTypeRef       ps_value;
214
215         int i;
216
217         char   name[128];
218         double charge  = INVALID_VALUE;
219         double current = INVALID_VALUE;
220         double voltage = INVALID_VALUE;
221
222         ps_raw       = IOPSCopyPowerSourcesInfo ();
223         ps_array     = IOPSCopyPowerSourcesList (ps_raw);
224         ps_array_len = CFArrayGetCount (ps_array);
225
226         DBG ("ps_array_len == %i", ps_array_len);
227
228         for (i = 0; i < ps_array_len; i++)
229         {
230                 ps_obj  = CFArrayGetValueAtIndex (ps_array, i);
231                 ps_dict = IOPSGetPowerSourceDescription (ps_raw, ps_obj);
232
233                 if (CFGetTypeID (ps_dict) != CFDictionaryGetTypeID ())
234                 {
235                         DBG ("IOPSGetPowerSourceDescription did not return a CFDictionaryRef");
236                         continue;
237                 }
238
239                 if (ps_dict != NULL)
240                 {
241                         /* Get the current capacity/charge */
242                         ps_value = NULL;
243                         charge   = INVALID_VALUE;
244                         if (CFDictionaryGetValueIfPresent (ps_dict,
245                                                 CFSTR (kIOPSCurrentCapacityKey),
246                                                 &ps_value))
247                         {
248                                 if (CFGetTypeID (ps_value) != CFNumberGetTypeID ())
249                                         CFNumberGetValue (ps_value,
250                                                         kCFNumberDoubleType,
251                                                         &charge);
252                                 else
253                                         DBG ("kIOPSCurrentCapacityKey: Not a CFNumber");
254
255                                 DBG ("charge = %f", charge);
256                         }
257                         else
258                                 DBG ("`%s' does not exist", kIOPSCurrentCapacityKey);
259
260                         /* Get the current */
261                         ps_value = NULL;
262                         current  = INVALID_VALUE;
263                         if (CFDictionaryGetValueIfPresent (ps_dict,
264                                                 CFSTR (kIOPSCurrentKey),
265                                                 &ps_value))
266                         {
267                                 if (CFGetTypeID (ps_value) != CFNumberGetTypeID ())
268                                         CFNumberGetValue (ps_value,
269                                                         kCFNumberDoubleType,
270                                                         &current);
271                                 else
272                                         DBG ("kIOPSCurrentKey: Not a CFNumber");
273                                 DBG ("current = %f", current);
274                         }
275                         else
276                                 DBG ("`%s' does not exist", kIOPSCurrentKey);
277
278                         /* Get the voltage */
279                         ps_value = NULL;
280                         voltage  = INVALID_VALUE;
281                         if (CFDictionaryGetValueIfPresent (ps_dict,
282                                                 CFSTR (kIOPSVoltageKey),
283                                                 &ps_value))
284                         {
285                                 if (CFGetTypeID (ps_value) != CFNumberGetTypeID ())
286                                         CFNumberGetValue (ps_value,
287                                                         kCFNumberDoubleType,
288                                                         &voltage);
289                                 else
290                                         DBG ("kIOPSVoltageKey: Not a CFNumber");
291                                 DBG ("voltage = %f", voltage);
292                         }
293                         else
294                                 DBG ("`%s' does not exist", kIOPSVoltageKey);
295
296                         /* Get the name of the device.. */
297                         ps_value = NULL;
298                         if (CFDictionaryGetValueIfPresent (ps_dict,
299                                                 CFSTR (kIOPSNameKey),
300                                                 &ps_value))
301                         {
302                                 if (CFGetTypeID (ps_value) != CFStringGetTypeID ())
303                                 {
304                                         if (!CFStringGetCString (ps_value,
305                                                                 name, 128,
306                                                                 kCFStringEncodingASCII))
307                                                 continue;
308                                 }
309                                 else
310                                 {
311                                         DBG ("kIOPSNameKey: Not a CFStringGetTypeID");
312                                 }
313                                 DBG ("Original string: `%s'", name);
314                         }
315                         else
316                         {
317                                 strncpy (name, "unknown", 128);
318                         }
319                         name[127] = '\0';
320                         for (i = 0; i < 128; i++)
321                         {
322                                 if (name[i] == '\0')
323                                         break;
324                                 else if (isalnum (name[i]))
325                                         name[i] = (char) tolower (name[i]);
326                                 else
327                                         name[i] = '_';
328                         }
329
330                         battery_submit (name, current, voltage, charge);
331                 }
332         }
333
334         CFRelease(ps_array);
335         CFRelease(ps_raw);
336 /* #endif HAVE_IOKIT_PS_IOPOWERSOURCES_H */
337
338 #elif KERNEL_LINUX
339         FILE *fh;
340         char buffer[BUFSIZE];
341         char filename[BUFSIZE];
342         
343         char *fields[8];
344         int numfields;
345
346         int i;
347         int len;
348
349         for (i = 0; i < battery_pmu_num; i++)
350         {
351                 char    batnum_str[BUFSIZE];
352                 double  current = INVALID_VALUE;
353                 double  voltage = INVALID_VALUE;
354                 double  charge  = INVALID_VALUE;
355                 double *valptr = NULL;
356
357                 len = snprintf (filename, BUFSIZE, battery_pmu_file, i);
358                 if ((len >= BUFSIZE) || (len < 0))
359                         continue;
360
361                 len = snprintf (batnum_str, BUFSIZE, "%i", i);
362                 if ((len >= BUFSIZE) || (len < 0))
363                         continue;
364
365                 if ((fh = fopen (filename, "r")) == NULL)
366                         continue;
367
368                 while (fgets (buffer, BUFSIZE, fh) != NULL)
369                 {
370                         numfields = strsplit (buffer, fields, 8);
371
372                         if (numfields < 3)
373                                 continue;
374
375                         if (strcmp ("current", fields[0]) == 0)
376                                 valptr = &current;
377                         else if (strcmp ("voltage", fields[0]) == 0)
378                                 valptr = &voltage;
379                         else if (strcmp ("charge", fields[0]) == 0)
380                                 valptr = &charge;
381                         else
382                                 valptr = NULL;
383
384                         if (valptr != NULL)
385                         {
386                                 char *endptr;
387
388                                 endptr = NULL;
389                                 errno  = 0;
390
391                                 *valptr = strtod (fields[2], &endptr) / 1000.0;
392
393                                 if ((fields[2] == endptr) || (errno != 0))
394                                         *valptr = INVALID_VALUE;
395                         }
396                 }
397
398                 if ((current != INVALID_VALUE)
399                                 || (voltage != INVALID_VALUE)
400                                 || (charge  != INVALID_VALUE))
401                         battery_submit (batnum_str, current, voltage, charge);
402
403                 fclose (fh);
404                 fh = NULL;
405         }
406
407         if (access ("/proc/acpi/battery", R_OK | X_OK) == 0)
408         {
409                 double  current = INVALID_VALUE;
410                 double  voltage = INVALID_VALUE;
411                 double  charge  = INVALID_VALUE;
412                 double *valptr = NULL;
413                 int charging = 0;
414
415                 struct dirent *ent;
416                 DIR *dh;
417
418                 if ((dh = opendir ("/proc/acpi/battery")) == NULL)
419                 {
420                         syslog (LOG_ERR, "Cannot open `/proc/acpi/battery': %s", strerror (errno));
421                         return;
422                 }
423
424                 while ((ent = readdir (dh)) != NULL)
425                 {
426                         if (ent->d_name[0] == '.')
427                                 continue;
428
429                         len = snprintf (filename, BUFSIZE, "/proc/acpi/battery/%s/state", ent->d_name);
430                         if ((len >= BUFSIZE) || (len < 0))
431                                 continue;
432
433                         if ((fh = fopen (filename, "r")) == NULL)
434                         {
435                                 syslog (LOG_ERR, "Cannot open `%s': %s", filename, strerror (errno));
436                                 continue;
437                         }
438
439                         /*
440                          * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
441                          * [11:00] <@tokkee> present:                 yes
442                          * [11:00] <@tokkee> capacity state:          ok
443                          * [11:00] <@tokkee> charging state:          charging
444                          * [11:00] <@tokkee> present rate:            1724 mA
445                          * [11:00] <@tokkee> remaining capacity:      4136 mAh
446                          * [11:00] <@tokkee> present voltage:         12428 mV
447                          */
448                         while (fgets (buffer, BUFSIZE, fh) != NULL)
449                         {
450                                 numfields = strsplit (buffer, fields, 8);
451
452                                 if (numfields < 3)
453                                         continue;
454
455                                 if ((strcmp (fields[0], "present") == 0)
456                                                 && (strcmp (fields[1], "rate:") == 0))
457                                         valptr = &current;
458                                 else if ((strcmp (fields[0], "remaining") == 0)
459                                                 && (strcmp (fields[1], "capacity:") == 0))
460                                         valptr = &charge;
461                                 else if ((strcmp (fields[0], "present") == 0)
462                                                 && (strcmp (fields[1], "voltage:") == 0))
463                                         valptr = &voltage;
464                                 else
465                                         valptr = NULL;
466
467                                 if ((strcmp (fields[0], "charging") == 0)
468                                                 && (strcmp (fields[1], "state:") == 0))
469                                 {
470                                         if (strcmp (fields[2], "charging") == 0)
471                                                 charging = 1;
472                                         else
473                                                 charging = 0;
474                                 }
475
476                                 if (valptr != NULL)
477                                 {
478                                         char *endptr;
479
480                                         endptr = NULL;
481                                         errno  = 0;
482
483                                         *valptr = strtod (fields[2], &endptr) / 1000.0;
484
485                                         if ((fields[2] == endptr) || (errno != 0))
486                                                 *valptr = INVALID_VALUE;
487                                 }
488                         }
489
490                         if ((current != INVALID_VALUE) && (charging == 0))
491                                         current *= -1;
492
493                         if ((current != INVALID_VALUE)
494                                         || (voltage != INVALID_VALUE)
495                                         || (charge  != INVALID_VALUE))
496                                 battery_submit (ent->d_name, current, voltage, charge);
497
498                         fclose (fh);
499                 }
500
501                 closedir (dh);
502         }
503 #endif /* KERNEL_LINUX */
504 }
505 #else
506 # define battery_read NULL
507 #endif /* BATTERY_HAVE_READ */
508
509 void module_register (void)
510 {
511         plugin_register (MODULE_NAME, battery_init, battery_read, NULL);
512         plugin_register ("battery_current", NULL, NULL, battery_current_write);
513         plugin_register ("battery_voltage", NULL, NULL, battery_voltage_write);
514         plugin_register ("battery_charge",  NULL, NULL, battery_charge_write);
515 }
516
517 #undef BUFSIZE
518 #undef MODULE_NAME