Merge branch 'collectd-5.4'
[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 #include "common.h"      /* rrd_update_file */
29 #include "plugin.h"      /* plugin_register, plugin_submit */
30 #include "configfile.h"  /* cf_register */
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_DEFAULT_NODE
44 # define APCUPS_DEFAULT_NODE "localhost"
45 #endif
46
47 #ifndef APCUPS_DEFAULT_SERVICE
48 # define APCUPS_DEFAULT_SERVICE "3551"
49 #endif
50
51 /*
52  * Private data types
53  */
54 struct apc_detail_s
55 {
56         double linev;
57         double loadpct;
58         double bcharge;
59         double timeleft;
60         double outputv;
61         double itemp;
62         double battv;
63         double linefreq;
64 };
65
66 /*
67  * Private variables
68  */
69 /* Default values for contacting daemon */
70 static char *conf_node = NULL;
71 static char *conf_service = NULL;
72 /* Defaults to false for backwards compatibility. */
73 static _Bool conf_report_seconds = 0;
74 static _Bool conf_persistent_conn = 1;
75
76 static int global_sockfd = -1;
77
78 static int count_retries = 0;
79 static int count_iterations = 0;
80
81 static int net_shutdown (int *fd)
82 {
83         uint16_t packet_size = 0;
84
85         if ((fd == NULL) || (*fd < 0))
86                 return (EINVAL);
87
88         swrite (*fd, (void *) &packet_size, sizeof (packet_size));
89         close (*fd);
90         *fd = -1;
91
92         return (0);
93 } /* int net_shutdown */
94
95 /* Close the network connection */
96 static int apcups_shutdown (void)
97 {
98         if (global_sockfd < 0)
99                 return (0);
100
101         net_shutdown (&global_sockfd);
102         return (0);
103 } /* int apcups_shutdown */
104
105 /*
106  * Open a TCP connection to the UPS network server
107  * Returns -1 on error
108  * Returns socket file descriptor otherwise
109  */
110 static int net_open (char const *node, char const *service)
111 {
112         int              sd;
113         int              status;
114         struct addrinfo  ai_hints;
115         struct addrinfo *ai_return;
116         struct addrinfo *ai_list;
117
118         /* Resolve name */
119         memset (&ai_hints, 0, sizeof (ai_hints));
120         /* TODO: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */
121         ai_hints.ai_family   = AF_INET;
122         ai_hints.ai_socktype = SOCK_STREAM;
123
124         status = getaddrinfo (node, service, &ai_hints, &ai_return);
125         if (status != 0)
126         {
127                 char errbuf[1024];
128                 INFO ("apcups plugin: getaddrinfo failed: %s",
129                                 (status == EAI_SYSTEM)
130                                 ? sstrerror (errno, errbuf, sizeof (errbuf))
131                                 : gai_strerror (status));
132                 return (-1);
133         }
134
135         /* Create socket */
136         sd = -1;
137         for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next)
138         {
139                 sd = socket (ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol);
140                 if (sd >= 0)
141                         break;
142         }
143         /* `ai_list' still holds the current description of the socket.. */
144
145         if (sd < 0)
146         {
147                 DEBUG ("apcups plugin: Unable to open a socket");
148                 freeaddrinfo (ai_return);
149                 return (-1);
150         }
151
152         status = connect (sd, ai_list->ai_addr, ai_list->ai_addrlen);
153
154         freeaddrinfo (ai_return);
155
156         if (status != 0) /* `connect(2)' failed */
157         {
158                 char errbuf[1024];
159                 INFO ("apcups plugin: connect failed: %s",
160                                 sstrerror (errno, errbuf, sizeof (errbuf)));
161                 close (sd);
162                 return (-1);
163         }
164
165         DEBUG ("apcups plugin: Done opening a socket %i", sd);
166
167         return (sd);
168 } /* int net_open */
169
170 /*
171  * Receive a message from the other end. Each message consists of
172  * two packets. The first is a header that contains the size
173  * of the data that follows in the second packet.
174  * Returns number of bytes read
175  * Returns 0 on end of file
176  * Returns -1 on hard end of file (i.e. network connection close)
177  * Returns -2 on error
178  */
179 static int net_recv (int *sockfd, char *buf, int buflen)
180 {
181         uint16_t packet_size;
182
183         /* get data size -- in short */
184         if (sread (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
185         {
186                 close (*sockfd);
187                 *sockfd = -1;
188                 return (-1);
189         }
190
191         packet_size = ntohs (packet_size);
192         if (packet_size > buflen)
193         {
194                 ERROR ("apcups plugin: Received %"PRIu16" bytes of payload "
195                                 "but have only %i bytes of buffer available.",
196                                 packet_size, buflen);
197                 close (*sockfd);
198                 *sockfd = -1;
199                 return (-2);
200         }
201
202         if (packet_size == 0)
203                 return (0);
204
205         /* now read the actual data */
206         if (sread (*sockfd, (void *) buf, packet_size) != 0)
207         {
208                 close (*sockfd);
209                 *sockfd = -1;
210                 return (-1);
211         }
212
213         return ((int) packet_size);
214 } /* static int net_recv (int *sockfd, char *buf, int buflen) */
215
216 /*
217  * Send a message over the network. The send consists of
218  * two network packets. The first is sends a short containing
219  * the length of the data packet which follows.
220  * Returns zero on success
221  * Returns non-zero on error
222  */
223 static int net_send (int *sockfd, char *buff, int len)
224 {
225         uint16_t packet_size;
226
227         assert (len > 0);
228         assert (*sockfd >= 0);
229
230         /* send short containing size of data packet */
231         packet_size = htons ((uint16_t) len);
232
233         if (swrite (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
234         {
235                 close (*sockfd);
236                 *sockfd = -1;
237                 return (-1);
238         }
239
240         /* send data packet */
241         if (swrite (*sockfd, (void *) buff, len) != 0)
242         {
243                 close (*sockfd);
244                 *sockfd = -1;
245                 return (-2);
246         }
247
248         return (0);
249 }
250
251 /* Get and print status from apcupsd NIS server */
252 static int apc_query_server (char const *node, char const *service,
253                 struct apc_detail_s *apcups_detail)
254 {
255         int     n;
256         char    recvline[1024];
257         char   *tokptr;
258         char   *toksaveptr;
259         char   *key;
260         double  value;
261         _Bool retry = 1;
262         int status;
263
264 #if APCMAIN
265 # define PRINT_VALUE(name, val) printf("  Found property: name = %s; value = %f;\n", name, val)
266 #else
267 # define PRINT_VALUE(name, val) /**/
268 #endif
269
270         while (retry)
271         {
272                 if (global_sockfd < 0)
273                 {
274                         global_sockfd = net_open (node, service);
275                         if (global_sockfd < 0)
276                         {
277                                 ERROR ("apcups plugin: Connecting to the "
278                                                 "apcupsd failed.");
279                                 return (-1);
280                         }
281                 }
282
283
284                 status = net_send (&global_sockfd, "status", strlen ("status"));
285                 if (status != 0)
286                 {
287                         /* net_send is closing the socket on error. */
288                         assert (global_sockfd < 0);
289                         if (retry)
290                         {
291                                 retry = 0;
292                                 count_retries++;
293                                 continue;
294                         }
295
296                         ERROR ("apcups plugin: Writing to the socket failed.");
297                         return (-1);
298                 }
299
300                 break;
301         } /* while (retry) */
302
303         /* When collectd's collection interval is larger than apcupsd's
304          * timeout, we would have to retry / re-connect each iteration. Try to
305          * detect this situation and shut down the socket gracefully in that
306          * case. Otherwise, keep the socket open to avoid overhead. */
307         count_iterations++;
308         if ((count_iterations == 10) && (count_retries > 2))
309         {
310                 NOTICE ("apcups plugin: There have been %i retries in the "
311                                 "first %i iterations. Will close the socket "
312                                 "in future iterations.",
313                                 count_retries, count_iterations);
314                 conf_persistent_conn = 0;
315         }
316
317         while ((n = net_recv (&global_sockfd, recvline, sizeof (recvline) - 1)) > 0)
318         {
319                 assert ((size_t)n < sizeof (recvline));
320                 recvline[n] = 0;
321 #if APCMAIN
322                 printf ("net_recv = `%s';\n", recvline);
323 #endif /* if APCMAIN */
324
325                 if (strncmp ("END APC", recvline, strlen ("END APC")) == 0)
326                         break;
327
328                 toksaveptr = NULL;
329                 tokptr = strtok_r (recvline, " :\t", &toksaveptr);
330                 while (tokptr != NULL)
331                 {
332                         key = tokptr;
333                         if ((tokptr = strtok_r (NULL, " :\t", &toksaveptr)) == NULL)
334                                 continue;
335                         value = atof (tokptr);
336
337                         PRINT_VALUE (key, value);
338
339                         if (strcmp ("LINEV", key) == 0)
340                                 apcups_detail->linev = value;
341                         else if (strcmp ("BATTV", key) == 0)
342                                 apcups_detail->battv = value;
343                         else if (strcmp ("ITEMP", key) == 0)
344                                 apcups_detail->itemp = value;
345                         else if (strcmp ("LOADPCT", key) == 0)
346                                 apcups_detail->loadpct = value;
347                         else if (strcmp ("BCHARGE", key) == 0)
348                                 apcups_detail->bcharge = value;
349                         else if (strcmp ("OUTPUTV", key) == 0)
350                                 apcups_detail->outputv = value;
351                         else if (strcmp ("LINEFREQ", key) == 0)
352                                 apcups_detail->linefreq = value;
353                         else if (strcmp ("TIMELEFT", key) == 0)
354                         {
355                                 /* Convert minutes to seconds if requested by
356                                  * the user. */
357                                 if (conf_report_seconds)
358                                         value *= 60.0;
359                                 apcups_detail->timeleft = value;
360                         }
361
362                         tokptr = strtok_r (NULL, ":", &toksaveptr);
363                 } /* while (tokptr != NULL) */
364         }
365         status = errno; /* save errno, net_shutdown() may re-set it. */
366
367         if (!conf_persistent_conn)
368                 net_shutdown (&global_sockfd);
369
370         if (n < 0)
371         {
372                 char errbuf[1024];
373                 ERROR ("apcups plugin: Reading from socket failed: %s",
374                                 sstrerror (status, errbuf, sizeof (errbuf)));
375                 return (-1);
376         }
377
378         return (0);
379 }
380
381 static int apcups_config (oconfig_item_t *ci)
382 {
383         int i;
384
385         for (i = 0; i < ci->children_num; i++)
386         {
387                 oconfig_item_t *child = ci->children + i;
388
389                 if (strcasecmp (child->key, "Host") == 0)
390                         cf_util_get_string (child, &conf_node);
391                 else if (strcasecmp (child->key, "Port") == 0)
392                         cf_util_get_service (child, &conf_service);
393                 else if (strcasecmp (child->key, "ReportSeconds") == 0)
394                         cf_util_get_boolean (child, &conf_report_seconds);
395                 else if (strcasecmp (child->key, "PersistentConnection") == 0)
396                         cf_util_get_boolean (child, &conf_persistent_conn);
397                 else
398                         ERROR ("apcups plugin: Unknown config option \"%s\".", child->key);
399         }
400
401         return (0);
402 } /* int apcups_config */
403
404 static void apc_submit_generic (char *type, char *type_inst, double value)
405 {
406         value_t values[1];
407         value_list_t vl = VALUE_LIST_INIT;
408
409         values[0].gauge = value;
410
411         vl.values = values;
412         vl.values_len = 1;
413         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
414         sstrncpy (vl.plugin, "apcups", sizeof (vl.plugin));
415         sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
416         sstrncpy (vl.type, type, sizeof (vl.type));
417         sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
418
419         plugin_dispatch_values (&vl);
420 }
421
422 static void apc_submit (struct apc_detail_s *apcups_detail)
423 {
424         apc_submit_generic ("voltage",    "input",   apcups_detail->linev);
425         apc_submit_generic ("voltage",    "output",  apcups_detail->outputv);
426         apc_submit_generic ("voltage",    "battery", apcups_detail->battv);
427         apc_submit_generic ("charge",     "",        apcups_detail->bcharge);
428         apc_submit_generic ("percent",    "load",    apcups_detail->loadpct);
429         apc_submit_generic ("timeleft",   "",        apcups_detail->timeleft);
430         apc_submit_generic ("temperature", "",       apcups_detail->itemp);
431         apc_submit_generic ("frequency",  "input",   apcups_detail->linefreq);
432 }
433
434 static int apcups_read (void)
435 {
436         struct apc_detail_s apcups_detail;
437         int status;
438
439         apcups_detail.linev    =   -1.0;
440         apcups_detail.outputv  =   -1.0;
441         apcups_detail.battv    =   -1.0;
442         apcups_detail.loadpct  =   -1.0;
443         apcups_detail.bcharge  =   -1.0;
444         apcups_detail.timeleft =    NAN;
445         apcups_detail.itemp    = -300.0;
446         apcups_detail.linefreq =   -1.0;
447
448         status = apc_query_server ((conf_node == NULL) ? APCUPS_DEFAULT_NODE : conf_node,
449                         (conf_service == NULL) ? APCUPS_DEFAULT_SERVICE : conf_service,
450                         &apcups_detail);
451
452         /*
453          * if we did not connect then do not bother submitting
454          * zeros. We want rrd files to have NAN.
455          */
456         if (status != 0)
457         {
458                 DEBUG ("apcups plugin: apc_query_server (%s, %s) = %i",
459                                 (conf_node == NULL) ? APCUPS_DEFAULT_NODE : conf_node,
460                                 (conf_service == NULL) ? APCUPS_DEFAULT_SERVICE : conf_service,
461                                 status);
462                 return (-1);
463         }
464
465         apc_submit (&apcups_detail);
466
467         return (0);
468 } /* apcups_read */
469
470 void module_register (void)
471 {
472         plugin_register_complex_config ("apcups", apcups_config);
473         plugin_register_read ("apcups", apcups_read);
474         plugin_register_shutdown ("apcups", apcups_shutdown);
475 } /* void module_register */