/** * collectd - src/battery.c * 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 * Free Software Foundation; only version 2 of the License is applicable. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Authors: * Florian octo Forster * Michał Mirosław * Andy Parkins **/ #include "collectd.h" #include "plugin.h" #include "utils/common/common.h" #if HAVE_MACH_MACH_TYPES_H #include #endif #if HAVE_MACH_MACH_INIT_H #include #endif #if HAVE_MACH_MACH_ERROR_H #include #endif #if HAVE_COREFOUNDATION_COREFOUNDATION_H #include #endif #if HAVE_IOKIT_IOKITLIB_H #include #endif #if HAVE_IOKIT_IOTYPES_H #include #endif #if HAVE_IOKIT_PS_IOPOWERSOURCES_H #include #endif #if HAVE_IOKIT_PS_IOPSKEYS_H #include #endif #if !HAVE_IOKIT_IOKITLIB_H && !HAVE_IOKIT_PS_IOPOWERSOURCES_H && !KERNEL_LINUX #error "No applicable input method." #endif #if HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H /* No global variables */ /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */ #elif KERNEL_LINUX #define PROC_PMU_PATH_FORMAT "/proc/pmu/battery_%i" #define PROC_ACPI_PATH "/proc/acpi/battery" #define PROC_ACPI_FACTOR 0.001 #define SYSFS_PATH "/sys/class/power_supply" #define SYSFS_FACTOR 0.000001 #endif /* KERNEL_LINUX */ int battery_read_statefs( void); /* defined in battery_statefs; used by StateFS backend */ static bool report_percent; static bool report_degraded; static bool query_statefs; static void battery_submit2(char const *plugin_instance, /* {{{ */ char const *type, char const *type_instance, gauge_t value) { value_list_t vl = VALUE_LIST_INIT; vl.values = &(value_t){.gauge = value}; vl.values_len = 1; sstrncpy(vl.plugin, "battery", sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, type, sizeof(vl.type)); if (type_instance != NULL) sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance)); plugin_dispatch_values(&vl); } /* }}} void battery_submit2 */ static void battery_submit(char const *plugin_instance, /* {{{ */ char const *type, gauge_t value) { battery_submit2(plugin_instance, type, NULL, value); } /* }}} void battery_submit */ static void submit_capacity(char const *plugin_instance, /* {{{ */ gauge_t capacity_charged, gauge_t capacity_full, gauge_t capacity_design) { if (report_percent && (capacity_charged > capacity_full)) return; if (report_degraded && (capacity_full > capacity_design)) return; if (report_percent) { gauge_t capacity_max; if (report_degraded) capacity_max = capacity_design; else capacity_max = capacity_full; battery_submit2(plugin_instance, "percent", "charged", 100.0 * capacity_charged / capacity_max); battery_submit2(plugin_instance, "percent", "discharged", 100.0 * (capacity_full - capacity_charged) / capacity_max); if (report_degraded) battery_submit2(plugin_instance, "percent", "degraded", 100.0 * (capacity_design - capacity_full) / capacity_max); } else if (report_degraded) /* && !report_percent */ { battery_submit2(plugin_instance, "capacity", "charged", capacity_charged); battery_submit2(plugin_instance, "capacity", "discharged", (capacity_full - capacity_charged)); battery_submit2(plugin_instance, "capacity", "degraded", (capacity_design - capacity_full)); } else /* !report_percent && !report_degraded */ { battery_submit(plugin_instance, "capacity", capacity_charged); } } /* }}} void submit_capacity */ #if HAVE_IOKIT_PS_IOPOWERSOURCES_H || HAVE_IOKIT_IOKITLIB_H static double dict_get_double(CFDictionaryRef dict, const char *key_string) /* {{{ */ { double val_double; long long val_int; CFNumberRef val_obj; CFStringRef key_obj; key_obj = CFStringCreateWithCString(kCFAllocatorDefault, key_string, kCFStringEncodingASCII); if (key_obj == NULL) { DEBUG("CFStringCreateWithCString (%s) failed.\n", key_string); return NAN; } if ((val_obj = CFDictionaryGetValue(dict, key_obj)) == NULL) { DEBUG("CFDictionaryGetValue (%s) failed.", key_string); CFRelease(key_obj); return NAN; } CFRelease(key_obj); if (CFGetTypeID(val_obj) == CFNumberGetTypeID()) { if (CFNumberIsFloatType(val_obj)) { CFNumberGetValue(val_obj, kCFNumberDoubleType, &val_double); } else { CFNumberGetValue(val_obj, kCFNumberLongLongType, &val_int); val_double = val_int; } } else { DEBUG("CFGetTypeID (val_obj) = %i", (int)CFGetTypeID(val_obj)); return NAN; } return val_double; } /* }}} double dict_get_double */ #if HAVE_IOKIT_PS_IOPOWERSOURCES_H static void get_via_io_power_sources(double *ret_charge, /* {{{ */ double *ret_current, double *ret_voltage) { CFTypeRef ps_raw; CFArrayRef ps_array; int ps_array_len; CFDictionaryRef ps_dict; CFTypeRef ps_obj; double temp_double; ps_raw = IOPSCopyPowerSourcesInfo(); ps_array = IOPSCopyPowerSourcesList(ps_raw); ps_array_len = CFArrayGetCount(ps_array); DEBUG("ps_array_len == %i", ps_array_len); for (int i = 0; i < ps_array_len; i++) { ps_obj = CFArrayGetValueAtIndex(ps_array, i); ps_dict = IOPSGetPowerSourceDescription(ps_raw, ps_obj); if (ps_dict == NULL) { DEBUG("IOPSGetPowerSourceDescription failed."); continue; } if (CFGetTypeID(ps_dict) != CFDictionaryGetTypeID()) { DEBUG("IOPSGetPowerSourceDescription did not return a CFDictionaryRef"); continue; } /* FIXME: Check if this is really an internal battery */ if (isnan(*ret_charge)) { /* This is the charge in percent. */ temp_double = dict_get_double(ps_dict, kIOPSCurrentCapacityKey); if (!isnan((temp_double)) && (temp_double >= 0.0) && (temp_double <= 100.0)) *ret_charge = temp_double; } if (isnan(*ret_current)) { temp_double = dict_get_double(ps_dict, kIOPSCurrentKey); if (!isnan(temp_double)) *ret_current = temp_double / 1000.0; } if (isnan(*ret_voltage)) { temp_double = dict_get_double(ps_dict, kIOPSVoltageKey); if (!isnan(temp_double)) *ret_voltage = temp_double / 1000.0; } } CFRelease(ps_array); CFRelease(ps_raw); } /* }}} 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_capacity_full, /* {{{ */ double *ret_capacity_design, double *ret_current, double *ret_voltage) { kern_return_t status; io_iterator_t iterator; io_object_t io_obj; CFDictionaryRef bat_root_dict; CFArrayRef bat_info_arry; CFIndex bat_info_arry_len; CFDictionaryRef bat_info_dict; double temp_double; status = IOServiceGetMatchingServices( kIOMasterPortDefault, IOServiceNameMatching("battery"), &iterator); if (status != kIOReturnSuccess) { DEBUG("IOServiceGetMatchingServices failed."); return; } while ((io_obj = IOIteratorNext(iterator))) { status = IORegistryEntryCreateCFProperties( io_obj, (CFMutableDictionaryRef *)&bat_root_dict, kCFAllocatorDefault, kNilOptions); if (status != kIOReturnSuccess) { DEBUG("IORegistryEntryCreateCFProperties failed."); continue; } bat_info_arry = (CFArrayRef)CFDictionaryGetValue(bat_root_dict, CFSTR("IOBatteryInfo")); if (bat_info_arry == NULL) { CFRelease(bat_root_dict); continue; } bat_info_arry_len = CFArrayGetCount(bat_info_arry); for (CFIndex bat_info_arry_pos = 0; bat_info_arry_pos < bat_info_arry_len; bat_info_arry_pos++) { bat_info_dict = (CFDictionaryRef)CFArrayGetValueAtIndex( bat_info_arry, bat_info_arry_pos); if (isnan(*ret_capacity_full)) { temp_double = dict_get_double(bat_info_dict, "Capacity"); *ret_capacity_full = temp_double / 1000.0; } if (isnan(*ret_capacity_design)) { temp_double = dict_get_double(bat_info_dict, "AbsoluteMaxCapacity"); *ret_capacity_design = temp_double / 1000.0; } if (isnan(*ret_current)) { temp_double = dict_get_double(bat_info_dict, "Current"); *ret_current = temp_double / 1000.0; } if (isnan(*ret_voltage)) { temp_double = dict_get_double(bat_info_dict, "Voltage"); *ret_voltage = temp_double / 1000.0; } } CFRelease(bat_root_dict); } IOObjectRelease(iterator); } /* }}} void get_via_generic_iokit */ #endif /* HAVE_IOKIT_IOKITLIB_H */ static int battery_read(void) /* {{{ */ { gauge_t current = NAN; /* Current in A */ gauge_t voltage = NAN; /* Voltage in V */ /* We only get the charged capacity as a percentage from * IOPowerSources. IOKit, on the other hand, only reports the full * capacity. We use the two to calculate the current charged capacity. */ gauge_t charge_rel = NAN; /* Current charge in percent */ gauge_t capacity_charged; /* Charged capacity */ gauge_t capacity_full = NAN; /* Total capacity */ gauge_t capacity_design = NAN; /* Full design capacity */ if (query_statefs) return battery_read_statefs(); #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(&capacity_full, &capacity_design, ¤t, &voltage); #endif capacity_charged = charge_rel * capacity_full / 100.0; submit_capacity("0", capacity_charged, capacity_full, capacity_design); if (!isnan(current)) battery_submit("0", "current", current); if (!isnan(voltage)) battery_submit("0", "voltage", voltage); return 0; } /* }}} int battery_read */ /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */ #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) { char filename[PATH_MAX]; int status; snprintf(filename, sizeof(filename), "%s/%s/%s", dir, power_supply, basename); status = (int)read_text_file_contents(filename, buffer, buffer_size); if (status < 0) return status; strstripnewline(buffer); 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_capacity(char const *dir, /* {{{ */ char const *power_supply, char const *plugin_instance) { gauge_t capacity_charged = NAN; gauge_t capacity_full = NAN; gauge_t capacity_design = NAN; int status; status = sysfs_file_to_gauge(dir, power_supply, "energy_now", &capacity_charged); if (status != 0) return status; status = sysfs_file_to_gauge(dir, power_supply, "energy_full", &capacity_full); if (status != 0) return status; status = sysfs_file_to_gauge(dir, power_supply, "energy_full_design", &capacity_design); if (status != 0) return status; submit_capacity(plugin_instance, capacity_charged * SYSFS_FACTOR, capacity_full * SYSFS_FACTOR, capacity_design * SYSFS_FACTOR); return 0; } /* }}} int read_sysfs_capacity */ static int read_sysfs_capacity_from_charge(char const *dir, /* {{{ */ char const *power_supply, char const *plugin_instance) { gauge_t capacity_charged = NAN; gauge_t capacity_full = NAN; gauge_t capacity_design = NAN; gauge_t voltage_min_design = NAN; int status; status = sysfs_file_to_gauge(dir, power_supply, "voltage_min_design", &voltage_min_design); if (status != 0) return status; voltage_min_design *= SYSFS_FACTOR; status = sysfs_file_to_gauge(dir, power_supply, "charge_now", &capacity_charged); if (status != 0) return status; capacity_charged *= voltage_min_design; status = sysfs_file_to_gauge(dir, power_supply, "charge_full", &capacity_full); if (status != 0) return status; capacity_full *= voltage_min_design; status = sysfs_file_to_gauge(dir, power_supply, "charge_full_design", &capacity_design); if (status != 0) return status; capacity_design *= voltage_min_design; submit_capacity(plugin_instance, capacity_charged * SYSFS_FACTOR, capacity_full * SYSFS_FACTOR, capacity_design * SYSFS_FACTOR); return 0; } /* }}} int read_sysfs_capacity_from_charge */ 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 = false; 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 = true; /* 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) read_sysfs_capacity(dir, power_supply, plugin_instance); else read_sysfs_capacity_from_charge(dir, power_supply, plugin_instance); if (sysfs_file_to_gauge(dir, power_supply, "power_now", &v) == 0) { if (discharging) v *= -1.0; battery_submit(plugin_instance, "power", v * SYSFS_FACTOR); } if (sysfs_file_to_gauge(dir, power_supply, "current_now", &v) == 0) { if (discharging) v *= -1.0; battery_submit(plugin_instance, "current", v * SYSFS_FACTOR); } if (sysfs_file_to_gauge(dir, power_supply, "voltage_now", &v) == 0) battery_submit(plugin_instance, "voltage", v * SYSFS_FACTOR); 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_full_capacity(char const *dir, /* {{{ */ char const *power_supply, gauge_t *ret_capacity_full, gauge_t *ret_capacity_design) { char filename[PATH_MAX]; char buffer[1024]; FILE *fh; snprintf(filename, sizeof(filename), "%s/%s/info", dir, power_supply); fh = fopen(filename, "r"); if (fh == NULL) return errno; /* last full capacity: 40090 mWh */ while (fgets(buffer, sizeof(buffer), fh) != NULL) { gauge_t *value_ptr; int fields_num; char *fields[8]; int index; if (strncmp("last full capacity:", buffer, strlen("last full capacity:")) == 0) { value_ptr = ret_capacity_full; index = 3; } else if (strncmp("design capacity:", buffer, strlen("design capacity:")) == 0) { value_ptr = ret_capacity_design; index = 2; } else { continue; } fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields)); if (fields_num <= index) continue; strtogauge(fields[index], value_ptr); } fclose(fh); return 0; } /* }}} int read_acpi_full_capacity */ static int read_acpi_callback(char const *dir, /* {{{ */ char const *power_supply, void *user_data) { int *battery_index = user_data; gauge_t power = NAN; gauge_t voltage = NAN; gauge_t capacity_charged = NAN; gauge_t capacity_full = NAN; gauge_t capacity_design = NAN; bool charging = false; bool is_current = false; char const *plugin_instance; char filename[PATH_MAX]; char buffer[1024]; FILE *fh; snprintf(filename, sizeof(filename), "%s/%s/state", dir, power_supply); fh = fopen(filename, "r"); if (fh == NULL) { if ((errno == EAGAIN) || (errno == EINTR) || (errno == ENOENT)) return 0; else return errno; } /* * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state * [11:00] <@tokkee> present: yes * [11:00] <@tokkee> capacity state: ok * [11:00] <@tokkee> charging state: charging * [11:00] <@tokkee> present rate: 1724 mA * [11:00] <@tokkee> remaining capacity: 4136 mAh * [11:00] <@tokkee> present voltage: 12428 mV */ while (fgets(buffer, sizeof(buffer), fh) != NULL) { char *fields[8]; int numfields; numfields = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields)); if (numfields < 3) continue; if ((strcmp(fields[0], "charging") == 0) && (strcmp(fields[1], "state:") == 0)) { if (strcmp(fields[2], "charging") == 0) charging = true; else charging = false; continue; } /* The unit of "present rate" depends on the battery. Modern * batteries export power (watts), older batteries (used to) * export current (amperes). We check the fourth column and try * to find old batteries this way. */ if ((strcmp(fields[0], "present") == 0) && (strcmp(fields[1], "rate:") == 0)) { strtogauge(fields[2], &power); if ((numfields >= 4) && (strcmp("mA", fields[3]) == 0)) is_current = true; } else if ((strcmp(fields[0], "remaining") == 0) && (strcmp(fields[1], "capacity:") == 0)) strtogauge(fields[2], &capacity_charged); else if ((strcmp(fields[0], "present") == 0) && (strcmp(fields[1], "voltage:") == 0)) strtogauge(fields[2], &voltage); } /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */ fclose(fh); if (!charging) power *= -1.0; /* 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)++; read_acpi_full_capacity(dir, power_supply, &capacity_full, &capacity_design); submit_capacity(plugin_instance, capacity_charged * PROC_ACPI_FACTOR, capacity_full * PROC_ACPI_FACTOR, capacity_design * PROC_ACPI_FACTOR); battery_submit(plugin_instance, is_current ? "current" : "power", power * PROC_ACPI_FACTOR); battery_submit(plugin_instance, "voltage", voltage * PROC_ACPI_FACTOR); return 0; } /* }}} int read_acpi_callback */ static int read_acpi(void) /* {{{ */ { int status; int battery_counter = 0; if (access(PROC_ACPI_PATH, R_OK) != 0) return ENOENT; status = walk_directory(PROC_ACPI_PATH, read_acpi_callback, /* user_data = */ &battery_counter, /* include hidden */ 0); return status; } /* }}} int read_acpi */ static int read_pmu(void) /* {{{ */ { int i = 0; /* 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 < 100; i++) { FILE *fh; char buffer[1024]; char filename[PATH_MAX]; char plugin_instance[DATA_MAX_NAME_LEN]; gauge_t current = NAN; gauge_t voltage = NAN; gauge_t charge = NAN; snprintf(filename, sizeof(filename), PROC_PMU_PATH_FORMAT, i); if (access(filename, R_OK) != 0) break; snprintf(plugin_instance, sizeof(plugin_instance), "%i", i); 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) { char *fields[8]; int numfields; numfields = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields)); if (numfields < 3) continue; if (strcmp("current", fields[0]) == 0) strtogauge(fields[2], ¤t); else if (strcmp("voltage", fields[0]) == 0) strtogauge(fields[2], &voltage); else if (strcmp("charge", fields[0]) == 0) strtogauge(fields[2], &charge); } fclose(fh); fh = NULL; 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 (i == 0) return ENOENT; return 0; } /* }}} int read_pmu */ static int battery_read(void) /* {{{ */ { int status; if (query_statefs) return battery_read_statefs(); 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: All available input methods failed."); return -1; } /* }}} int battery_read */ #endif /* KERNEL_LINUX */ static int battery_config(oconfig_item_t *ci) { for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *child = ci->children + i; if (strcasecmp("ValuesPercentage", child->key) == 0) cf_util_get_boolean(child, &report_percent); else if (strcasecmp("ReportDegraded", child->key) == 0) cf_util_get_boolean(child, &report_degraded); else if (strcasecmp("QueryStateFS", child->key) == 0) cf_util_get_boolean(child, &query_statefs); else WARNING("battery plugin: Ignoring unknown " "configuration option \"%s\".", child->key); } return 0; } /* }}} int battery_config */ void module_register(void) { plugin_register_complex_config("battery", battery_config); plugin_register_read("battery", battery_read); } /* void module_register */