Merge pull request #2742 from elfiesmelfie/ipmi_bugfix_sensor_option
[collectd.git] / src / apcups.c
1 /*
2  * collectd - src/apcups.c
3  * Copyright (C) 2006-2015  Florian octo Forster
4  * Copyright (C) 2006       Anthony Gialluca <tonyabg at charter.net>
5  * Copyright (C) 2000-2004  Kern Sibbald
6  * Copyright (C) 1996-1999  Andre M. Hedrick <andre at suse.com>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2 of the GNU General
10  * Public License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public
18  * License along with this program; if not, write to the Free
19  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
20  * MA 02111-1307, USA.
21  *
22  * Authors:
23  *   Anthony Gialluca <tonyabg at charter.net>
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h" /* rrd_update_file */
30 #include "plugin.h" /* plugin_register, plugin_submit */
31
32 #if HAVE_SYS_TYPES_H
33 #include <sys/types.h>
34 #endif
35 #if HAVE_NETDB_H
36 #include <netdb.h>
37 #endif
38
39 #if HAVE_NETINET_IN_H
40 #include <netinet/in.h>
41 #endif
42
43 #ifndef APCUPS_SERVER_TIMEOUT
44 #define APCUPS_SERVER_TIMEOUT 15.0
45 #endif
46
47 #ifndef APCUPS_DEFAULT_NODE
48 #define APCUPS_DEFAULT_NODE "localhost"
49 #endif
50
51 #ifndef APCUPS_DEFAULT_SERVICE
52 #define APCUPS_DEFAULT_SERVICE "3551"
53 #endif
54
55 /*
56  * Private data types
57  */
58 typedef struct {
59   gauge_t linev;
60   gauge_t loadpct;
61   gauge_t bcharge;
62   gauge_t timeleft;
63   gauge_t outputv;
64   gauge_t itemp;
65   gauge_t battv;
66   gauge_t linefreq;
67 } apc_detail_t;
68
69 /*
70  * Private variables
71  */
72 /* Default values for contacting daemon */
73 static char *conf_node = NULL;
74 static char *conf_service = NULL;
75 /* Defaults to false for backwards compatibility. */
76 static _Bool conf_report_seconds = 0;
77 static _Bool conf_persistent_conn = 1;
78
79 static int global_sockfd = -1;
80
81 static int count_retries = 0;
82 static int count_iterations = 0;
83
84 static int net_shutdown(int *fd) {
85   uint16_t packet_size = 0;
86
87   if ((fd == NULL) || (*fd < 0))
88     return EINVAL;
89
90   (void)swrite(*fd, (void *)&packet_size, sizeof(packet_size));
91   close(*fd);
92   *fd = -1;
93
94   return 0;
95 } /* int net_shutdown */
96
97 /* Close the network connection */
98 static int apcups_shutdown(void) {
99   if (global_sockfd < 0)
100     return 0;
101
102   net_shutdown(&global_sockfd);
103   return 0;
104 } /* int apcups_shutdown */
105
106 /*
107  * Open a TCP connection to the UPS network server
108  * Returns -1 on error
109  * Returns socket file descriptor otherwise
110  */
111 static int net_open(char const *node, char const *service) {
112   int sd;
113   int status;
114   struct addrinfo *ai_return;
115   struct addrinfo *ai_list;
116
117   /* TODO: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */
118   struct addrinfo ai_hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM};
119
120   status = getaddrinfo(node, service, &ai_hints, &ai_return);
121   if (status != 0) {
122     char errbuf[1024];
123     INFO("apcups plugin: getaddrinfo failed: %s",
124          (status == EAI_SYSTEM) ? sstrerror(errno, errbuf, sizeof(errbuf))
125                                 : gai_strerror(status));
126     return -1;
127   }
128
129   /* Create socket */
130   sd = -1;
131   for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next) {
132     sd = socket(ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol);
133     if (sd >= 0)
134       break;
135   }
136   /* `ai_list' still holds the current description of the socket.. */
137
138   if (sd < 0) {
139     DEBUG("apcups plugin: Unable to open a socket");
140     freeaddrinfo(ai_return);
141     return -1;
142   }
143
144   status = connect(sd, ai_list->ai_addr, ai_list->ai_addrlen);
145
146   freeaddrinfo(ai_return);
147
148   if (status != 0) /* `connect(2)' failed */
149   {
150     char errbuf[1024];
151     INFO("apcups plugin: connect failed: %s",
152          sstrerror(errno, errbuf, sizeof(errbuf)));
153     close(sd);
154     return -1;
155   }
156
157   DEBUG("apcups plugin: Done opening a socket %i", sd);
158
159   return sd;
160 } /* int net_open */
161
162 /*
163  * Receive a message from the other end. Each message consists of
164  * two packets. The first is a header that contains the size
165  * of the data that follows in the second packet.
166  * Returns number of bytes read
167  * Returns 0 on end of file
168  * Returns -1 on hard end of file (i.e. network connection close)
169  * Returns -2 on error
170  */
171 static int net_recv(int *sockfd, char *buf, int buflen) {
172   uint16_t packet_size;
173
174   /* get data size -- in short */
175   if (sread(*sockfd, (void *)&packet_size, sizeof(packet_size)) != 0) {
176     close(*sockfd);
177     *sockfd = -1;
178     return -1;
179   }
180
181   packet_size = ntohs(packet_size);
182   if (packet_size > buflen) {
183     ERROR("apcups plugin: Received %" PRIu16 " bytes of payload "
184           "but have only %i bytes of buffer available.",
185           packet_size, buflen);
186     close(*sockfd);
187     *sockfd = -1;
188     return -2;
189   }
190
191   if (packet_size == 0)
192     return 0;
193
194   /* now read the actual data */
195   if (sread(*sockfd, (void *)buf, packet_size) != 0) {
196     close(*sockfd);
197     *sockfd = -1;
198     return -1;
199   }
200
201   return (int)packet_size;
202 } /* static int net_recv (int *sockfd, char *buf, int buflen) */
203
204 /*
205  * Send a message over the network. The send consists of
206  * two network packets. The first is sends a short containing
207  * the length of the data packet which follows.
208  * Returns zero on success
209  * Returns non-zero on error
210  */
211 static int net_send(int *sockfd, const char *buff, int len) {
212   uint16_t packet_size;
213
214   assert(len > 0);
215   assert(*sockfd >= 0);
216
217   /* send short containing size of data packet */
218   packet_size = htons((uint16_t)len);
219
220   if (swrite(*sockfd, (void *)&packet_size, sizeof(packet_size)) != 0) {
221     close(*sockfd);
222     *sockfd = -1;
223     return -1;
224   }
225
226   /* send data packet */
227   if (swrite(*sockfd, (void *)buff, len) != 0) {
228     close(*sockfd);
229     *sockfd = -1;
230     return -2;
231   }
232
233   return 0;
234 }
235
236 /* Get and print status from apcupsd NIS server */
237 static int apc_query_server(char const *node, char const *service,
238                             apc_detail_t *apcups_detail) {
239   int n;
240   char recvline[1024];
241   char *tokptr;
242   char *toksaveptr;
243   int try = 0;
244   int status;
245
246 #if APCMAIN
247 #define PRINT_VALUE(name, val)                                                 \
248   printf("  Found property: name = %s; value = %f;\n", name, val)
249 #else
250 #define PRINT_VALUE(name, val) /**/
251 #endif
252
253   while (1) {
254     if (global_sockfd < 0) {
255       global_sockfd = net_open(node, service);
256       if (global_sockfd < 0) {
257         ERROR("apcups plugin: Connecting to the "
258               "apcupsd failed.");
259         return -1;
260       }
261     }
262
263     status = net_send(&global_sockfd, "status", strlen("status"));
264     if (status != 0) {
265       /* net_send closes the socket on error. */
266       assert(global_sockfd < 0);
267       if (try == 0) {
268         try++;
269         count_retries++;
270         continue;
271       }
272
273       ERROR("apcups plugin: Writing to the socket failed.");
274       return -1;
275     }
276
277     break;
278   } /* while (1) */
279
280   /* When collectd's collection interval is larger than apcupsd's
281    * timeout, we would have to retry / re-connect each iteration. Try to
282    * detect this situation and shut down the socket gracefully in that
283    * case. Otherwise, keep the socket open to avoid overhead. */
284   count_iterations++;
285   if ((count_iterations == 10) && (count_retries > 2)) {
286     NOTICE("apcups plugin: There have been %i retries in the "
287            "first %i iterations. Will close the socket "
288            "in future iterations.",
289            count_retries, count_iterations);
290     conf_persistent_conn = 0;
291   }
292
293   while ((n = net_recv(&global_sockfd, recvline, sizeof(recvline) - 1)) > 0) {
294     assert((size_t)n < sizeof(recvline));
295     recvline[n] = 0;
296 #if APCMAIN
297     printf("net_recv = `%s';\n", recvline);
298 #endif /* if APCMAIN */
299
300     toksaveptr = NULL;
301     tokptr = strtok_r(recvline, " :\t", &toksaveptr);
302     while (tokptr != NULL) {
303       char *key = tokptr;
304       if ((tokptr = strtok_r(NULL, " :\t", &toksaveptr)) == NULL)
305         continue;
306
307       gauge_t value;
308       if (strtogauge(tokptr, &value) != 0)
309         continue;
310
311       PRINT_VALUE(key, value);
312
313       if (strcmp("LINEV", key) == 0)
314         apcups_detail->linev = value;
315       else if (strcmp("BATTV", key) == 0)
316         apcups_detail->battv = value;
317       else if (strcmp("ITEMP", key) == 0)
318         apcups_detail->itemp = value;
319       else if (strcmp("LOADPCT", key) == 0)
320         apcups_detail->loadpct = value;
321       else if (strcmp("BCHARGE", key) == 0)
322         apcups_detail->bcharge = value;
323       else if (strcmp("OUTPUTV", key) == 0)
324         apcups_detail->outputv = value;
325       else if (strcmp("LINEFREQ", key) == 0)
326         apcups_detail->linefreq = value;
327       else if (strcmp("TIMELEFT", key) == 0) {
328         /* Convert minutes to seconds if requested by
329          * the user. */
330         if (conf_report_seconds)
331           value *= 60.0;
332         apcups_detail->timeleft = value;
333       }
334
335       tokptr = strtok_r(NULL, ":", &toksaveptr);
336     } /* while (tokptr != NULL) */
337   }
338   status = errno; /* save errno, net_shutdown() may re-set it. */
339
340   if (!conf_persistent_conn)
341     net_shutdown(&global_sockfd);
342
343   if (n < 0) {
344     char errbuf[1024];
345     ERROR("apcups plugin: Reading from socket failed: %s",
346           sstrerror(status, errbuf, sizeof(errbuf)));
347     return -1;
348   }
349
350   return 0;
351 }
352
353 static int apcups_config(oconfig_item_t *ci) {
354   _Bool persistent_conn_set = 0;
355
356   for (int i = 0; i < ci->children_num; i++) {
357     oconfig_item_t *child = ci->children + i;
358
359     if (strcasecmp(child->key, "Host") == 0)
360       cf_util_get_string(child, &conf_node);
361     else if (strcasecmp(child->key, "Port") == 0)
362       cf_util_get_service(child, &conf_service);
363     else if (strcasecmp(child->key, "ReportSeconds") == 0)
364       cf_util_get_boolean(child, &conf_report_seconds);
365     else if (strcasecmp(child->key, "PersistentConnection") == 0) {
366       cf_util_get_boolean(child, &conf_persistent_conn);
367       persistent_conn_set = 1;
368     } else
369       ERROR("apcups plugin: Unknown config option \"%s\".", child->key);
370   }
371
372   if (!persistent_conn_set) {
373     double interval = CDTIME_T_TO_DOUBLE(plugin_get_interval());
374     if (interval > APCUPS_SERVER_TIMEOUT) {
375       NOTICE("apcups plugin: Plugin poll interval set to %.3f seconds. "
376              "Apcupsd NIS socket timeout is %.3f seconds, "
377              "PersistentConnection disabled by default.",
378              interval, APCUPS_SERVER_TIMEOUT);
379       conf_persistent_conn = 0;
380     }
381   }
382
383   return 0;
384 } /* int apcups_config */
385
386 static void apc_submit_generic(const char *type, const char *type_inst,
387                                gauge_t value) {
388   if (isnan(value))
389     return;
390
391   value_list_t vl = VALUE_LIST_INIT;
392   vl.values = &(value_t){.gauge = value};
393   vl.values_len = 1;
394   sstrncpy(vl.plugin, "apcups", sizeof(vl.plugin));
395   sstrncpy(vl.type, type, sizeof(vl.type));
396   sstrncpy(vl.type_instance, type_inst, sizeof(vl.type_instance));
397
398   plugin_dispatch_values(&vl);
399 }
400
401 static void apc_submit(apc_detail_t const *apcups_detail) {
402   apc_submit_generic("voltage", "input", apcups_detail->linev);
403   apc_submit_generic("voltage", "output", apcups_detail->outputv);
404   apc_submit_generic("voltage", "battery", apcups_detail->battv);
405   apc_submit_generic("charge", "", apcups_detail->bcharge);
406   apc_submit_generic("percent", "load", apcups_detail->loadpct);
407   apc_submit_generic("timeleft", "", apcups_detail->timeleft);
408   apc_submit_generic("temperature", "", apcups_detail->itemp);
409   apc_submit_generic("frequency", "input", apcups_detail->linefreq);
410 }
411
412 static int apcups_read(void) {
413   apc_detail_t apcups_detail = {
414       .linev = NAN,
415       .outputv = NAN,
416       .battv = NAN,
417       .loadpct = NAN,
418       .bcharge = NAN,
419       .timeleft = NAN,
420       .itemp = NAN,
421       .linefreq = NAN,
422   };
423
424   int status = apc_query_server(conf_node, conf_service, &apcups_detail);
425
426   if (status != 0) {
427     DEBUG("apcups plugin: apc_query_server (\"%s\", \"%s\") = %d",
428           conf_node, conf_service, status);
429     return status;
430   }
431
432   apc_submit(&apcups_detail);
433
434   return 0;
435 } /* apcups_read */
436
437 static int apcups_init(void) {
438   if (conf_node == NULL)
439     conf_node = APCUPS_DEFAULT_NODE;
440
441   if (conf_service == NULL)
442     conf_service = APCUPS_DEFAULT_SERVICE;
443
444   return 0;
445 } /* apcups_init */
446
447 void module_register(void) {
448   plugin_register_complex_config("apcups", apcups_config);
449   plugin_register_init("apcups", apcups_init);
450   plugin_register_read("apcups", apcups_read);
451   plugin_register_shutdown("apcups", apcups_shutdown);
452 } /* void module_register */