summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 9901983)
raw | patch | inline | side by side (parent: 9901983)
author | Florian Forster <octo@collectd.org> | |
Wed, 10 Sep 2014 14:53:51 +0000 (16:53 +0200) | ||
committer | Florian Forster <octo@collectd.org> | |
Wed, 10 Sep 2014 14:57:03 +0000 (16:57 +0200) |
This started as a simple import of Andy Parkins' sysfsbattery plugin into
the battery plugin. Since the battery plugin is ancient and hasn't been
touched in a while, this quickly escalated to a much bigger refactoring.
Sorry!
On the other hand, this fixes a couple of bugs. For example, all metrics
were always dispatched with plugin_instance "0". This is correct for the
majority of laptops, of course, but in theory this could be wrong.
Also ACPI charging / discharging rate is reported as "current", when
modern batteries actually report "power". The sysfs code does this
correctly, ACPI still needs to be patched.
Fixes: #725
the battery plugin. Since the battery plugin is ancient and hasn't been
touched in a while, this quickly escalated to a much bigger refactoring.
Sorry!
On the other hand, this fixes a couple of bugs. For example, all metrics
were always dispatched with plugin_instance "0". This is correct for the
majority of laptops, of course, but in theory this could be wrong.
Also ACPI charging / discharging rate is reported as "current", when
modern batteries actually report "power". The sysfs code does this
correctly, ACPI still needs to be patched.
Fixes: #725
src/battery.c | patch | blob | history |
diff --git a/src/battery.c b/src/battery.c
index ce5818144138ce609ff70f1c48e9ac3c01889075..1d0757d3796accc53845a6d433188114b30ac7bd 100644 (file)
--- a/src/battery.c
+++ b/src/battery.c
/**
* collectd - src/battery.c
- * Copyright (C) 2006,2007 Florian octo Forster
+ * Copyright (C) 2006-2014 Florian octo Forster
* Copyright (C) 2008 Michał Mirosław
+ * Copyright (C) 2014 Andy Parkins
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Authors:
* Florian octo Forster <octo at collectd.org>
* Michał Mirosław <mirq-linux at rere.qmqm.pl>
+ * Andy Parkins <andyp at fussylogic.co.uk>
**/
#include "collectd.h"
/* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
#elif KERNEL_LINUX
-static int battery_pmu_num = 0;
-static char *battery_pmu_file = "/proc/pmu/battery_%i";
-static const char *battery_acpi_dir = "/proc/acpi/battery";
+# define PROC_PMU_PATH_FORMAT "/proc/pmu/battery_%i"
+# define PROC_ACPI_PATH "/proc/acpi/battery"
+# define SYSFS_PATH "/sys/class/power_supply"
#endif /* KERNEL_LINUX */
-static int battery_init (void)
-{
-#if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
- /* No init necessary */
-/* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
-
-#elif KERNEL_LINUX
- int len;
- char filename[128];
-
- for (battery_pmu_num = 0; ; battery_pmu_num++)
- {
- len = ssnprintf (filename, sizeof (filename), battery_pmu_file, battery_pmu_num);
-
- if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
- break;
-
- if (access (filename, R_OK))
- break;
- }
-#endif /* KERNEL_LINUX */
-
- return (0);
-}
-
-static void battery_submit (const char *plugin_instance, const char *type, double value)
+static void battery_submit (char const *plugin_instance, /* {{{ */
+ const char *type, gauge_t value)
{
value_t values[1];
value_list_t vl = VALUE_LIST_INIT;
@@ -108,10 +86,10 @@ static void battery_submit (const char *plugin_instance, const char *type, doubl
sstrncpy (vl.type, type, sizeof (vl.type));
plugin_dispatch_values (&vl);
-} /* void battery_submit */
+} /* }}} void battery_submit */
#if HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H
-double dict_get_double (CFDictionaryRef dict, char *key_string)
+static double dict_get_double (CFDictionaryRef dict, char *key_string) /* {{{ */
{
double val_double;
long long val_int;
CFStringRef key_obj;
key_obj = CFStringCreateWithCString (kCFAllocatorDefault, key_string,
- kCFStringEncodingASCII);
+ kCFStringEncodingASCII);
if (key_obj == NULL)
{
DEBUG ("CFStringCreateWithCString (%s) failed.\n", key_string);
}
return (val_double);
-}
-#endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H */
+} /* }}} double dict_get_double */
-#if HAVE_IOKIT_PS_IOPOWERSOURCES_H
-static void get_via_io_power_sources (double *ret_charge,
+# if HAVE_IOKIT_PS_IOPOWERSOURCES_H
+static void get_via_io_power_sources (double *ret_charge, /* {{{ */
double *ret_current,
double *ret_voltage)
{
CFRelease(ps_array);
CFRelease(ps_raw);
-}
-#endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
+} /* }}} void get_via_io_power_sources */
+# endif /* HAVE_IOKIT_PS_IOPOWERSOURCES_H */
-#if HAVE_IOKIT_IOKITLIB_H
-static void get_via_generic_iokit (double *ret_charge,
+# if HAVE_IOKIT_IOKITLIB_H
+static void get_via_generic_iokit (double *ret_charge, /* {{{ */
double *ret_current,
double *ret_voltage)
{
bat_info_arry_len = CFArrayGetCount (bat_info_arry);
for (bat_info_arry_pos = 0;
- bat_info_arry_pos < bat_info_arry_len;
- bat_info_arry_pos++)
+ bat_info_arry_pos < bat_info_arry_len;
+ bat_info_arry_pos++)
{
bat_info_dict = (CFDictionaryRef) CFArrayGetValueAtIndex (bat_info_arry, bat_info_arry_pos);
}
IOObjectRelease (iterator);
-}
-#endif /* HAVE_IOKIT_IOKITLIB_H */
+} /* }}} void get_via_generic_iokit */
+# endif /* HAVE_IOKIT_IOKITLIB_H */
-#if KERNEL_LINUX
-static int battery_read_acpi (const char __attribute__((unused)) *dir,
- const char *name, void __attribute__((unused)) *user_data)
+static int battery_read (void) /* {{{ */
{
- double current = INVALID_VALUE;
- double voltage = INVALID_VALUE;
- double charge = INVALID_VALUE;
- double *valptr = NULL;
- int charging = 0;
+ double charge = INVALID_VALUE; /* Current charge in Ah */
+ double current = INVALID_VALUE; /* Current in A */
+ double voltage = INVALID_VALUE; /* Voltage in V */
- char filename[256];
- FILE *fh;
+ double charge_rel = INVALID_VALUE; /* Current charge in percent */
+ double charge_abs = INVALID_VALUE; /* Total capacity */
- char buffer[1024];
- char *fields[8];
- int numfields;
- char *endptr;
- int len;
+#if HAVE_IOKIT_PS_IOPOWERSOURCES_H
+ get_via_io_power_sources (&charge_rel, ¤t, &voltage);
+#endif
+#if HAVE_IOKIT_IOKITLIB_H
+ get_via_generic_iokit (&charge_abs, ¤t, &voltage);
+#endif
+
+ if ((charge_rel != INVALID_VALUE) && (charge_abs != INVALID_VALUE))
+ charge = charge_abs * charge_rel / 100.0;
+
+ if (charge != INVALID_VALUE)
+ battery_submit ("0", "charge", charge);
+ if (current != INVALID_VALUE)
+ battery_submit ("0", "current", current);
+ if (voltage != INVALID_VALUE)
+ battery_submit ("0", "voltage", voltage);
+} /* }}} int battery_read */
+/* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
- len = ssnprintf (filename, sizeof (filename), "%s/%s/state", battery_acpi_dir, name);
+#elif KERNEL_LINUX
+/* Reads a file which contains only a number (and optionally a trailing
+ * newline) and parses that number. */
+static int sysfs_file_to_buffer(char const *dir, /* {{{ */
+ char const *power_supply,
+ char const *basename,
+ char *buffer, size_t buffer_size)
+{
+ int status;
+ FILE *fp;
+ char filename[PATH_MAX];
+
+ ssnprintf (filename, sizeof (filename), "%s/%s/%s",
+ dir, power_supply, basename);
- if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
- return -1;
+ /* No file isn't the end of the world -- not every system will be
+ * reporting the same set of statistics */
+ if (access (filename, R_OK) != 0)
+ return ENOENT;
- if ((fh = fopen (filename, "r")) == NULL) {
+ fp = fopen (filename, "r");
+ if (fp == NULL)
+ {
+ status = errno;
+ if (status != ENOENT)
+ {
+ char errbuf[1024];
+ WARNING ("battery plugin: fopen (%s) failed: %s", filename,
+ sstrerror (status, errbuf, sizeof (errbuf)));
+ }
+ return status;
+ }
+
+ if (fgets (buffer, buffer_size, fp) == NULL)
+ {
char errbuf[1024];
- ERROR ("Cannot open `%s': %s", filename,
- sstrerror (errno, errbuf, sizeof (errbuf)));
- return -1;
+ status = errno;
+ WARNING ("battery plugin: fgets failed: %s",
+ sstrerror (status, errbuf, sizeof (errbuf)));
+ fclose (fp);
+ return status;
+ }
+
+ strstripnewline (buffer);
+
+ fclose (fp);
+ return 0;
+} /* }}} int sysfs_file_to_buffer */
+
+/* Reads a file which contains only a number (and optionally a trailing
+ * newline) and parses that number. */
+static int sysfs_file_to_gauge(char const *dir, /* {{{ */
+ char const *power_supply,
+ char const *basename, gauge_t *ret_value)
+{
+ int status;
+ char buffer[32] = "";
+
+ status = sysfs_file_to_buffer (dir, power_supply, basename, buffer, sizeof (buffer));
+ if (status != 0)
+ return (status);
+
+ return (strtogauge (buffer, ret_value));
+} /* }}} sysfs_file_to_gauge */
+
+static int read_sysfs_callback (char const *dir, /* {{{ */
+ char const *power_supply,
+ void *user_data)
+{
+ int *battery_index = user_data;
+
+ char const *plugin_instance;
+ char buffer[32];
+ gauge_t v = NAN;
+ _Bool discharging = 0;
+ int status;
+
+ /* Ignore non-battery directories, such as AC power. */
+ status = sysfs_file_to_buffer (dir, power_supply, "type", buffer, sizeof (buffer));
+ if (status != 0)
+ return (0);
+ if (strcasecmp ("Battery", buffer) != 0)
+ return (0);
+
+ (void) sysfs_file_to_buffer (dir, power_supply, "status", buffer, sizeof (buffer));
+ if (strcasecmp ("Discharging", buffer) == 0)
+ discharging = 1;
+
+ /* FIXME: This is a dirty hack for backwards compatibility: The battery
+ * plugin, for a very long time, has had the plugin_instance
+ * hard-coded to "0". So, to keep backwards compatibility, we'll use
+ * "0" for the first battery we find and the power_supply name for all
+ * following. This should be reverted in a future major version. */
+ plugin_instance = (*battery_index == 0) ? "0" : power_supply;
+ (*battery_index)++;
+
+ if (sysfs_file_to_gauge (dir, power_supply, "energy_now", &v) == 0)
+ battery_submit (plugin_instance, "charge", v / 1000000.0);
+ if (sysfs_file_to_gauge (dir, power_supply, "power_now", &v) == 0)
+ {
+ if (discharging)
+ battery_submit (plugin_instance, "power", v / -1000000.0);
+ else
+ battery_submit (plugin_instance, "power", v / 1000000.0);
+ }
+ if (sysfs_file_to_gauge (dir, power_supply, "voltage_now", &v) == 0)
+ battery_submit (plugin_instance, "voltage", v / 1000000.0);
+#if 0
+ if (sysfs_file_to_gauge (dir, power_supply, "energy_full_design", &v) == 0)
+ battery_submit (plugin_instance, "charge", v / 1000000.0);
+ if (sysfs_file_to_gauge (dir, power_supply, "energy_full", &v) == 0)
+ battery_submit (plugin_instance, "charge", v / 1000000.0);
+ if (sysfs_file_to_gauge (dir, power_supply, "voltage_min_design", &v) == 0)
+ battery_submit (plugin_instance, "voltage", v / 1000000.0);
+#endif
+
+ return (0);
+} /* }}} int read_sysfs_callback */
+
+static int read_sysfs (void) /* {{{ */
+{
+ int status;
+ int battery_counter = 0;
+
+ if (access (SYSFS_PATH, R_OK) != 0)
+ return (ENOENT);
+
+ status = walk_directory (SYSFS_PATH, read_sysfs_callback,
+ /* user_data = */ &battery_counter,
+ /* include hidden */ 0);
+ return (status);
+} /* }}} int read_sysfs */
+
+static int read_acpi_callback (char const *dir, /* {{{ */
+ char const *power_supply,
+ void *user_data)
+{
+ int *battery_index = user_data;
+
+ gauge_t current = NAN;
+ gauge_t voltage = NAN;
+ gauge_t charge = NAN;
+ _Bool charging = 0;
+
+ char const *plugin_instance;
+ char filename[PATH_MAX];
+ char buffer[1024];
+
+ FILE *fh;
+
+ ssnprintf (filename, sizeof (filename), "%s/%s/state", dir, power_supply);
+ fh = fopen (filename, "r");
+ if ((fh = fopen (filename, "r")) == NULL)
+ {
+ if ((errno == EAGAIN) || (errno == EINTR) || (errno == ENOENT))
+ return (0);
+ else
+ return (errno);
}
/*
*/
while (fgets (buffer, sizeof (buffer), fh) != NULL)
{
- numfields = strsplit (buffer, fields, 8);
+ char *fields[8];
+ int numfields;
+ numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
if (numfields < 3)
continue;
continue;
}
+ /* FIXME: The unit of "present rate" depends on the battery.
+ * Modern batteries export watts, not amperes, i.e. it's not a
+ * current anymore. We should check if the fourth column
+ * contains "mA" and only use a current then. Otherwise, export
+ * a power. */
if ((strcmp (fields[0], "present") == 0)
&& (strcmp (fields[1], "rate:") == 0))
- valptr = ¤t;
+ strtogauge (fields[2], ¤t);
else if ((strcmp (fields[0], "remaining") == 0)
&& (strcmp (fields[1], "capacity:") == 0))
- valptr = &charge;
+ strtogauge (fields[2], &charge);
else if ((strcmp (fields[0], "present") == 0)
&& (strcmp (fields[1], "voltage:") == 0))
- valptr = &voltage;
- else
- continue;
-
- endptr = NULL;
- errno = 0;
- *valptr = strtod (fields[2], &endptr) / 1000.0;
-
- if ((fields[2] == endptr) || (errno != 0))
- *valptr = INVALID_VALUE;
+ strtogauge (fields[2], &voltage);
} /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */
fclose (fh);
- if ((current != INVALID_VALUE) && (charging == 0))
- current *= -1;
+ if (!charging)
+ current *= -1.0;
- if (charge != INVALID_VALUE)
- battery_submit ("0", "charge", charge);
- if (current != INVALID_VALUE)
- battery_submit ("0", "current", current);
- if (voltage != INVALID_VALUE)
- battery_submit ("0", "voltage", voltage);
+ /* FIXME: This is a dirty hack for backwards compatibility: The battery
+ * plugin, for a very long time, has had the plugin_instance
+ * hard-coded to "0". So, to keep backwards compatibility, we'll use
+ * "0" for the first battery we find and the power_supply name for all
+ * following. This should be reverted in a future major version. */
+ plugin_instance = (*battery_index == 0) ? "0" : power_supply;
+ (*battery_index)++;
- return 0;
-}
-#endif /* KERNEL_LINUX */
+ battery_submit (plugin_instance, "charge", charge / 1000.0);
+ battery_submit (plugin_instance, "current", current / 1000.0);
+ battery_submit (plugin_instance, "voltage", voltage / 1000.0);
+ return 0;
+} /* }}} int read_acpi_callback */
-static int battery_read (void)
+static int read_acpi (void) /* {{{ */
{
-#if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H
- double charge = INVALID_VALUE; /* Current charge in Ah */
- double current = INVALID_VALUE; /* Current in A */
- double voltage = INVALID_VALUE; /* Voltage in V */
+ int status;
+ int battery_counter = 0;
- double charge_rel = INVALID_VALUE; /* Current charge in percent */
- double charge_abs = INVALID_VALUE; /* Total capacity */
-
-#if HAVE_IOKIT_PS_IOPOWERSOURCES_H
- get_via_io_power_sources (&charge_rel, ¤t, &voltage);
-#endif
-#if HAVE_IOKIT_IOKITLIB_H
- get_via_generic_iokit (&charge_abs, ¤t, &voltage);
-#endif
+ if (access (PROC_ACPI_PATH, R_OK) != 0)
+ return (ENOENT);
- if ((charge_rel != INVALID_VALUE) && (charge_abs != INVALID_VALUE))
- charge = charge_abs * charge_rel / 100.0;
+ status = walk_directory (PROC_ACPI_PATH, read_acpi_callback,
+ /* user_data = */ &battery_counter,
+ /* include hidden */ 0);
+ return (status);
+} /* }}} int read_acpi */
- if (charge != INVALID_VALUE)
- battery_submit ("0", "charge", charge);
- if (current != INVALID_VALUE)
- battery_submit ("0", "current", current);
- if (voltage != INVALID_VALUE)
- battery_submit ("0", "voltage", voltage);
-/* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
+static int read_pmu (void) /* {{{ */
+{
+ int i;
-#elif KERNEL_LINUX
- static c_complain_t acpi_dir_complaint = C_COMPLAIN_INIT_STATIC;
+ /* The upper limit here is just a safeguard. If there is a system with
+ * more than 100 batteries, this can easily be increased. */
+ for (i = 0; i < 100; i++)
+ {
+ FILE *fh;
- FILE *fh;
- char buffer[1024];
- char filename[256];
-
- char *fields[8];
- int numfields;
+ char buffer[1024];
+ char filename[PATH_MAX];
+ char plugin_instance[DATA_MAX_NAME_LEN];
- int i;
- int len;
+ gauge_t current = NAN;
+ gauge_t voltage = NAN;
+ gauge_t charge = NAN;
- for (i = 0; i < battery_pmu_num; i++)
- {
- char batnum_str[256];
- double current = INVALID_VALUE;
- double voltage = INVALID_VALUE;
- double charge = INVALID_VALUE;
- double *valptr = NULL;
-
- len = ssnprintf (filename, sizeof (filename), battery_pmu_file, i);
- if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
- continue;
+ ssnprintf (filename, sizeof (filename), PROC_PMU_PATH_FORMAT, i);
+ if (access (filename, R_OK) != 0)
+ break;
- len = ssnprintf (batnum_str, sizeof (batnum_str), "%i", i);
- if ((len < 0) || ((unsigned int)len >= sizeof (batnum_str)))
- continue;
+ ssnprintf (plugin_instance, sizeof (plugin_instance), "%i", i);
- if ((fh = fopen (filename, "r")) == NULL)
- continue;
+ fh = fopen (filename, "r");
+ if (fh == NULL)
+ {
+ if (errno == ENOENT)
+ break;
+ else if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ else
+ return (errno);
+ }
while (fgets (buffer, sizeof (buffer), fh) != NULL)
{
- numfields = strsplit (buffer, fields, 8);
+ char *fields[8];
+ int numfields;
+ numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
if (numfields < 3)
continue;
if (strcmp ("current", fields[0]) == 0)
- valptr = ¤t;
+ strtogauge (fields[2], ¤t);
else if (strcmp ("voltage", fields[0]) == 0)
- valptr = &voltage;
+ strtogauge (fields[2], &voltage);
else if (strcmp ("charge", fields[0]) == 0)
- valptr = &charge;
- else
- valptr = NULL;
-
- if (valptr != NULL)
- {
- char *endptr;
-
- endptr = NULL;
- errno = 0;
-
- *valptr = strtod (fields[2], &endptr) / 1000.0;
-
- if ((fields[2] == endptr) || (errno != 0))
- *valptr = INVALID_VALUE;
- }
+ strtogauge (fields[2], &charge);
}
fclose (fh);
fh = NULL;
- if (charge != INVALID_VALUE)
- battery_submit ("0", "charge", charge);
- if (current != INVALID_VALUE)
- battery_submit ("0", "current", current);
- if (voltage != INVALID_VALUE)
- battery_submit ("0", "voltage", voltage);
+ battery_submit (plugin_instance, "charge", charge / 1000.0);
+ battery_submit (plugin_instance, "current", current / 1000.0);
+ battery_submit (plugin_instance, "voltage", voltage / 1000.0);
}
- if (0 == access (battery_acpi_dir, R_OK))
- walk_directory (battery_acpi_dir, battery_read_acpi,
- /* user_data = */ NULL,
- /* include hidden */ 0);
- else
- {
- char errbuf[1024];
- c_complain_once (LOG_WARNING, &acpi_dir_complaint,
- "battery plugin: Failed to access `%s': %s",
- battery_acpi_dir,
- sstrerror (errno, errbuf, sizeof (errbuf)));
- }
+ if (i == 0)
+ return (ENOENT);
+ return (0);
+} /* }}} int read_pmu */
+static int battery_read (void) /* {{{ */
+{
+ int status;
+
+ DEBUG ("battery plugin: Trying sysfs ...");
+ status = read_sysfs ();
+ if (status == 0)
+ return (0);
+
+ DEBUG ("battery plugin: Trying acpi ...");
+ status = read_acpi ();
+ if (status == 0)
+ return (0);
+
+ DEBUG ("battery plugin: Trying pmu ...");
+ status = read_pmu ();
+ if (status == 0)
+ return (0);
+
+ ERROR ("battery plugin: Add available input methods failed.");
+ return (-1);
+} /* }}} int battery_read */
#endif /* KERNEL_LINUX */
- return (0);
-}
-
void module_register (void)
{
- plugin_register_init ("battery", battery_init);
plugin_register_read ("battery", battery_read);
} /* void module_register */