/* * collectd - src/apcups.c * Copyright (C) 2006-2015 Florian octo Forster * Copyright (C) 2006 Anthony Gialluca * Copyright (C) 2000-2004 Kern Sibbald * Copyright (C) 1996-1999 Andre M. Hedrick * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General * Public License as published by the Free Software Foundation. * * 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., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. * * Authors: * Anthony Gialluca * Florian octo Forster **/ #include "collectd.h" #include "plugin.h" /* plugin_register, plugin_submit */ #include "utils/common/common.h" /* rrd_update_file */ #if HAVE_SYS_TYPES_H #include #endif #if HAVE_NETDB_H #include #endif #if HAVE_NETINET_IN_H #include #endif #ifndef APCUPS_SERVER_TIMEOUT #define APCUPS_SERVER_TIMEOUT 15.0 #endif #ifndef APCUPS_DEFAULT_NODE #define APCUPS_DEFAULT_NODE "localhost" #endif #ifndef APCUPS_DEFAULT_SERVICE #define APCUPS_DEFAULT_SERVICE "3551" #endif /* * Private data types */ typedef struct { gauge_t linev; gauge_t loadpct; gauge_t bcharge; gauge_t timeleft; gauge_t outputv; gauge_t itemp; gauge_t battv; gauge_t linefreq; } apc_detail_t; /* * Private variables */ /* Default values for contacting daemon */ static char *conf_node; static char *conf_service; /* Defaults to false for backwards compatibility. */ static bool conf_report_seconds; static bool conf_persistent_conn = true; static int global_sockfd = -1; static int count_retries; static int count_iterations; static int net_shutdown(int *fd) { uint16_t packet_size = 0; if ((fd == NULL) || (*fd < 0)) return EINVAL; (void)swrite(*fd, (void *)&packet_size, sizeof(packet_size)); close(*fd); *fd = -1; return 0; } /* int net_shutdown */ /* Close the network connection */ static int apcups_shutdown(void) { if (global_sockfd < 0) return 0; net_shutdown(&global_sockfd); return 0; } /* int apcups_shutdown */ /* * Open a TCP connection to the UPS network server * Returns -1 on error * Returns socket file descriptor otherwise */ static int net_open(char const *node, char const *service) { int sd; int status; struct addrinfo *ai_return; struct addrinfo *ai_list; /* TODO: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */ struct addrinfo ai_hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM}; status = getaddrinfo(node, service, &ai_hints, &ai_return); if (status != 0) { INFO("apcups plugin: getaddrinfo failed: %s", (status == EAI_SYSTEM) ? STRERRNO : gai_strerror(status)); return -1; } /* Create socket */ sd = -1; for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next) { sd = socket(ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol); if (sd >= 0) break; } /* `ai_list' still holds the current description of the socket.. */ if (sd < 0) { DEBUG("apcups plugin: Unable to open a socket"); freeaddrinfo(ai_return); return -1; } status = connect(sd, ai_list->ai_addr, ai_list->ai_addrlen); freeaddrinfo(ai_return); if (status != 0) /* `connect(2)' failed */ { INFO("apcups plugin: connect failed: %s", STRERRNO); close(sd); return -1; } DEBUG("apcups plugin: Done opening a socket %i", sd); return sd; } /* int net_open */ /* * Receive a message from the other end. Each message consists of * two packets. The first is a header that contains the size * of the data that follows in the second packet. * Returns number of bytes read * Returns 0 on end of file * Returns -1 on hard end of file (i.e. network connection close) * Returns -2 on error */ static int net_recv(int *sockfd, char *buf, int buflen) { uint16_t packet_size; /* get data size -- in short */ if (sread(*sockfd, (void *)&packet_size, sizeof(packet_size)) != 0) { close(*sockfd); *sockfd = -1; return -1; } packet_size = ntohs(packet_size); if (packet_size > buflen) { ERROR("apcups plugin: Received %" PRIu16 " bytes of payload " "but have only %i bytes of buffer available.", packet_size, buflen); close(*sockfd); *sockfd = -1; return -2; } if (packet_size == 0) return 0; /* now read the actual data */ if (sread(*sockfd, (void *)buf, packet_size) != 0) { close(*sockfd); *sockfd = -1; return -1; } return (int)packet_size; } /* static int net_recv (int *sockfd, char *buf, int buflen) */ /* * Send a message over the network. The send consists of * two network packets. The first is sends a short containing * the length of the data packet which follows. * Returns zero on success * Returns non-zero on error */ static int net_send(int *sockfd, const char *buff, int len) { uint16_t packet_size; assert(len > 0); assert(*sockfd >= 0); /* send short containing size of data packet */ packet_size = htons((uint16_t)len); if (swrite(*sockfd, (void *)&packet_size, sizeof(packet_size)) != 0) { close(*sockfd); *sockfd = -1; return -1; } /* send data packet */ if (swrite(*sockfd, (void *)buff, len) != 0) { close(*sockfd); *sockfd = -1; return -2; } return 0; } /* Get and print status from apcupsd NIS server */ static int apc_query_server(char const *node, char const *service, apc_detail_t *apcups_detail) { int n; char recvline[1024]; char *tokptr; char *toksaveptr; int try = 0; int status; #if APCMAIN #define PRINT_VALUE(name, val) \ printf(" Found property: name = %s; value = %f;\n", name, val) #else #define PRINT_VALUE(name, val) /**/ #endif while (1) { if (global_sockfd < 0) { global_sockfd = net_open(node, service); if (global_sockfd < 0) { ERROR("apcups plugin: Connecting to the " "apcupsd failed."); return -1; } } status = net_send(&global_sockfd, "status", strlen("status")); if (status != 0) { /* net_send closes the socket on error. */ assert(global_sockfd < 0); if (try == 0) { try ++; count_retries++; continue; } ERROR("apcups plugin: Writing to the socket failed."); return -1; } break; } /* while (1) */ /* When collectd's collection interval is larger than apcupsd's * timeout, we would have to retry / re-connect each iteration. Try to * detect this situation and shut down the socket gracefully in that * case. Otherwise, keep the socket open to avoid overhead. */ count_iterations++; if ((count_iterations == 10) && (count_retries > 2)) { NOTICE("apcups plugin: There have been %i retries in the " "first %i iterations. Will close the socket " "in future iterations.", count_retries, count_iterations); conf_persistent_conn = false; } while ((n = net_recv(&global_sockfd, recvline, sizeof(recvline) - 1)) > 0) { assert((size_t)n < sizeof(recvline)); recvline[n] = 0; #if APCMAIN printf("net_recv = `%s';\n", recvline); #endif /* if APCMAIN */ toksaveptr = NULL; tokptr = strtok_r(recvline, " :\t", &toksaveptr); while (tokptr != NULL) { char *key = tokptr; if ((tokptr = strtok_r(NULL, " :\t", &toksaveptr)) == NULL) continue; gauge_t value; if (strtogauge(tokptr, &value) != 0) continue; PRINT_VALUE(key, value); if (strcmp("LINEV", key) == 0) apcups_detail->linev = value; else if (strcmp("BATTV", key) == 0) apcups_detail->battv = value; else if (strcmp("ITEMP", key) == 0) apcups_detail->itemp = value; else if (strcmp("LOADPCT", key) == 0) apcups_detail->loadpct = value; else if (strcmp("BCHARGE", key) == 0) apcups_detail->bcharge = value; else if (strcmp("OUTPUTV", key) == 0) apcups_detail->outputv = value; else if (strcmp("LINEFREQ", key) == 0) apcups_detail->linefreq = value; else if (strcmp("TIMELEFT", key) == 0) { /* Convert minutes to seconds if requested by * the user. */ if (conf_report_seconds) value *= 60.0; apcups_detail->timeleft = value; } tokptr = strtok_r(NULL, ":", &toksaveptr); } /* while (tokptr != NULL) */ } status = errno; /* save errno, net_shutdown() may re-set it. */ if (!conf_persistent_conn) net_shutdown(&global_sockfd); if (n < 0) { ERROR("apcups plugin: Reading from socket failed: %s", STRERROR(status)); return -1; } return 0; } static int apcups_config(oconfig_item_t *ci) { bool persistent_conn_set = false; for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *child = ci->children + i; if (strcasecmp(child->key, "Host") == 0) cf_util_get_string(child, &conf_node); else if (strcasecmp(child->key, "Port") == 0) cf_util_get_service(child, &conf_service); else if (strcasecmp(child->key, "ReportSeconds") == 0) cf_util_get_boolean(child, &conf_report_seconds); else if (strcasecmp(child->key, "PersistentConnection") == 0) { cf_util_get_boolean(child, &conf_persistent_conn); persistent_conn_set = true; } else ERROR("apcups plugin: Unknown config option \"%s\".", child->key); } if (!persistent_conn_set) { double interval = CDTIME_T_TO_DOUBLE(plugin_get_interval()); if (interval > APCUPS_SERVER_TIMEOUT) { NOTICE("apcups plugin: Plugin poll interval set to %.3f seconds. " "Apcupsd NIS socket timeout is %.3f seconds, " "PersistentConnection disabled by default.", interval, APCUPS_SERVER_TIMEOUT); conf_persistent_conn = false; } } return 0; } /* int apcups_config */ static void apc_submit_generic(const char *type, const char *type_inst, gauge_t value) { if (isnan(value)) return; value_list_t vl = VALUE_LIST_INIT; vl.values = &(value_t){.gauge = value}; vl.values_len = 1; sstrncpy(vl.plugin, "apcups", sizeof(vl.plugin)); sstrncpy(vl.type, type, sizeof(vl.type)); sstrncpy(vl.type_instance, type_inst, sizeof(vl.type_instance)); plugin_dispatch_values(&vl); } static void apc_submit(apc_detail_t const *apcups_detail) { apc_submit_generic("voltage", "input", apcups_detail->linev); apc_submit_generic("voltage", "output", apcups_detail->outputv); apc_submit_generic("voltage", "battery", apcups_detail->battv); apc_submit_generic("charge", "", apcups_detail->bcharge); apc_submit_generic("percent", "load", apcups_detail->loadpct); apc_submit_generic("timeleft", "", apcups_detail->timeleft); apc_submit_generic("temperature", "", apcups_detail->itemp); apc_submit_generic("frequency", "input", apcups_detail->linefreq); } static int apcups_read(void) { apc_detail_t apcups_detail = { .linev = NAN, .outputv = NAN, .battv = NAN, .loadpct = NAN, .bcharge = NAN, .timeleft = NAN, .itemp = NAN, .linefreq = NAN, }; int status = apc_query_server(conf_node, conf_service, &apcups_detail); if (status != 0) { DEBUG("apcups plugin: apc_query_server (\"%s\", \"%s\") = %d", conf_node, conf_service, status); return status; } apc_submit(&apcups_detail); return 0; } /* apcups_read */ static int apcups_init(void) { if (conf_node == NULL) conf_node = APCUPS_DEFAULT_NODE; if (conf_service == NULL) conf_service = APCUPS_DEFAULT_SERVICE; return 0; } /* apcups_init */ void module_register(void) { plugin_register_complex_config("apcups", apcups_config); plugin_register_init("apcups", apcups_init); plugin_register_read("apcups", apcups_read); plugin_register_shutdown("apcups", apcups_shutdown); } /* void module_register */