Merged `branches/log-mode' to `trunk'
[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                                         if (!CFStringGetCString (ps_value,
304                                                                 name, 128,
305                                                                 kCFStringEncodingASCII))
306                                                 continue;
307                                 else
308                                         DBG ("kIOPSNameKey: Not a CFStringGetTypeID");
309                                 DBG ("Original string: `%s'", name);
310                         }
311                         else
312                         {
313                                 strncpy (name, "unknown", 128);
314                         }
315                         name[127] = '\0';
316                         for (i = 0; i < 128; i++)
317                         {
318                                 if (name[i] == '\0')
319                                         break;
320                                 else if (isalnum (name[i]))
321                                         name[i] = (char) tolower (name[i]);
322                                 else
323                                         name[i] = '_';
324                         }
325
326                         battery_submit (name, current, voltage, charge);
327                 }
328         }
329
330         CFRelease(ps_array);
331         CFRelease(ps_raw);
332 /* #endif HAVE_IOKIT_PS_IOPOWERSOURCES_H */
333
334 #elif KERNEL_LINUX
335         FILE *fh;
336         char buffer[BUFSIZE];
337         char filename[BUFSIZE];
338         
339         char *fields[8];
340         int numfields;
341
342         int i;
343         int len;
344
345         for (i = 0; i < battery_pmu_num; i++)
346         {
347                 char    batnum_str[BUFSIZE];
348                 double  current = INVALID_VALUE;
349                 double  voltage = INVALID_VALUE;
350                 double  charge  = INVALID_VALUE;
351                 double *valptr = NULL;
352
353                 len = snprintf (filename, BUFSIZE, battery_pmu_file, i);
354                 if ((len >= BUFSIZE) || (len < 0))
355                         continue;
356
357                 len = snprintf (batnum_str, BUFSIZE, "%i", i);
358                 if ((len >= BUFSIZE) || (len < 0))
359                         continue;
360
361                 if ((fh = fopen (filename, "r")) == NULL)
362                         continue;
363
364                 while (fgets (buffer, BUFSIZE, fh) != NULL)
365                 {
366                         numfields = strsplit (buffer, fields, 8);
367
368                         if (numfields < 3)
369                                 continue;
370
371                         if (strcmp ("current", fields[0]) == 0)
372                                 valptr = &current;
373                         else if (strcmp ("voltage", fields[0]) == 0)
374                                 valptr = &voltage;
375                         else if (strcmp ("charge", fields[0]) == 0)
376                                 valptr = &charge;
377                         else
378                                 valptr = NULL;
379
380                         if (valptr != NULL)
381                         {
382                                 char *endptr;
383
384                                 endptr = NULL;
385                                 errno  = 0;
386
387                                 *valptr = strtod (fields[2], &endptr) / 1000.0;
388
389                                 if ((fields[2] == endptr) || (errno != 0))
390                                         *valptr = INVALID_VALUE;
391                         }
392                 }
393
394                 if ((current != INVALID_VALUE)
395                                 || (voltage != INVALID_VALUE)
396                                 || (charge  != INVALID_VALUE))
397                         battery_submit (batnum_str, current, voltage, charge);
398
399                 fclose (fh);
400                 fh = NULL;
401         }
402
403         if (access ("/proc/acpi/battery", R_OK | X_OK) == 0)
404         {
405                 double  current = INVALID_VALUE;
406                 double  voltage = INVALID_VALUE;
407                 double  charge  = INVALID_VALUE;
408                 double *valptr = NULL;
409                 int charging = 0;
410
411                 struct dirent *ent;
412                 DIR *dh;
413
414                 if ((dh = opendir ("/proc/acpi/battery")) == NULL)
415                 {
416                         syslog (LOG_ERR, "Cannot open `/proc/acpi/battery': %s", strerror (errno));
417                         return;
418                 }
419
420                 while ((ent = readdir (dh)) != NULL)
421                 {
422                         if (ent->d_name[0] == '.')
423                                 continue;
424
425                         len = snprintf (filename, BUFSIZE, "/proc/acpi/battery/%s/state", ent->d_name);
426                         if ((len >= BUFSIZE) || (len < 0))
427                                 continue;
428
429                         if ((fh = fopen (filename, "r")) == NULL)
430                         {
431                                 syslog (LOG_ERR, "Cannot open `%s': %s", filename, strerror (errno));
432                                 continue;
433                         }
434
435                         /*
436                          * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
437                          * [11:00] <@tokkee> present:                 yes
438                          * [11:00] <@tokkee> capacity state:          ok
439                          * [11:00] <@tokkee> charging state:          charging
440                          * [11:00] <@tokkee> present rate:            1724 mA
441                          * [11:00] <@tokkee> remaining capacity:      4136 mAh
442                          * [11:00] <@tokkee> present voltage:         12428 mV
443                          */
444                         while (fgets (buffer, BUFSIZE, fh) != NULL)
445                         {
446                                 numfields = strsplit (buffer, fields, 8);
447
448                                 if (numfields < 3)
449                                         continue;
450
451                                 if ((strcmp (fields[0], "present") == 0)
452                                                 && (strcmp (fields[1], "rate:") == 0))
453                                         valptr = &current;
454                                 else if ((strcmp (fields[0], "remaining") == 0)
455                                                 && (strcmp (fields[1], "capacity:") == 0))
456                                         valptr = &charge;
457                                 else if ((strcmp (fields[0], "present") == 0)
458                                                 && (strcmp (fields[1], "voltage:") == 0))
459                                         valptr = &voltage;
460                                 else
461                                         valptr = NULL;
462
463                                 if ((strcmp (fields[0], "charging") == 0)
464                                                 && (strcmp (fields[1], "state:") == 0))
465                                 {
466                                         if (strcmp (fields[2], "charging") == 0)
467                                                 charging = 1;
468                                         else
469                                                 charging = 0;
470                                 }
471
472                                 if (valptr != NULL)
473                                 {
474                                         char *endptr;
475
476                                         endptr = NULL;
477                                         errno  = 0;
478
479                                         *valptr = strtod (fields[2], &endptr) / 1000.0;
480
481                                         if ((fields[2] == endptr) || (errno != 0))
482                                                 *valptr = INVALID_VALUE;
483                                 }
484                         }
485
486                         if ((current != INVALID_VALUE) && (charging == 0))
487                                         current *= -1;
488
489                         if ((current != INVALID_VALUE)
490                                         || (voltage != INVALID_VALUE)
491                                         || (charge  != INVALID_VALUE))
492                                 battery_submit (ent->d_name, current, voltage, charge);
493
494                         fclose (fh);
495                 }
496
497                 closedir (dh);
498         }
499 #endif /* KERNEL_LINUX */
500 }
501 #else
502 # define battery_read NULL
503 #endif /* BATTERY_HAVE_READ */
504
505 void module_register (void)
506 {
507         plugin_register (MODULE_NAME, battery_init, battery_read, NULL);
508         plugin_register ("battery_current", NULL, NULL, battery_current_write);
509         plugin_register ("battery_voltage", NULL, NULL, battery_voltage_write);
510         plugin_register ("battery_charge",  NULL, NULL, battery_charge_write);
511 }
512
513 #undef BUFSIZE
514 #undef MODULE_NAME