Merge branch 'collectd-3.11' into collectd-4.0
[collectd.git] / src / apcups.c
1 /*
2  * collectd - src/apcups.c
3  * Copyright (C) 2006 Anthony Gialluca <tonyabg at charter.net>
4  * Copyright (C) 2000-2004 Kern Sibbald
5  * Copyright (C) 1996-99 Andre M. Hedrick <andre at suse.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU General
9  * Public License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
19  * MA 02111-1307, USA.
20  *
21  * Authors:
22  *   Anthony Gialluca <tonyabg at charter.net>
23  **/
24
25 /*
26  * FIXME: Don't know why but without this here atof() was not returning
27  * correct values for me. This is behavior that I don't understand and
28  * should be examined in closer detail.
29  */
30 #include <stdlib.h>
31
32 #include "collectd.h"
33 #include "common.h"      /* rrd_update_file */
34 #include "plugin.h"      /* plugin_register, plugin_submit */
35 #include "configfile.h"  /* cf_register */
36
37 #if HAVE_SYS_TYPES_H
38 # include <sys/types.h>
39 #endif
40 #if HAVE_SYS_SOCKET_H
41 # include <sys/socket.h>
42 #endif
43 #if HAVE_NETDB_H
44 # include <netdb.h>
45 #endif
46
47 #if HAVE_NETINET_IN_H
48 # include <netinet/in.h>
49 #endif
50
51 #define NISPORT 3551
52 #define MAXSTRING               256
53 #define MODULE_NAME "apcups"
54
55 #define APCUPS_DEFAULT_HOST "localhost"
56
57 /*
58  * Private data types
59  */
60 struct apc_detail_s
61 {
62         double linev;
63         double loadpct;
64         double bcharge;
65         double timeleft;
66         double outputv;
67         double itemp;
68         double battv;
69         double linefreq;
70 };
71
72 /*
73  * Private variables
74  */
75 /* Default values for contacting daemon */
76 static char *conf_host = NULL;
77 static int   conf_port = NISPORT;
78
79 static int global_sockfd = -1;
80
81 static const char *config_keys[] =
82 {
83         "Host",
84         "Port",
85         NULL
86 };
87 static int config_keys_num = 2;
88
89 /* Close the network connection */
90 static int apcups_shutdown (void)
91 {
92         uint16_t packet_size = 0;
93
94         if (global_sockfd < 0)
95                 return (0);
96
97         DEBUG ("Gracefully shutting down socket %i.", global_sockfd);
98
99         /* send EOF sentinel */
100         swrite (global_sockfd, (void *) &packet_size, sizeof (packet_size));
101
102         close (global_sockfd);
103         global_sockfd = -1;
104
105         return (0);
106 } /* int apcups_shutdown */
107
108 /*     
109  * Open a TCP connection to the UPS network server
110  * Returns -1 on error
111  * Returns socket file descriptor otherwise
112  */
113 static int net_open (char *host, char *service, int port)
114 {
115         int              sd;
116         int              status;
117         char             port_str[8];
118         struct addrinfo  ai_hints;
119         struct addrinfo *ai_return;
120         struct addrinfo *ai_list;
121
122         assert ((port > 0x00000000) && (port <= 0x0000FFFF));
123
124         /* Convert the port to a string */
125         snprintf (port_str, 8, "%i", port);
126         port_str[7] = '\0';
127
128         /* Resolve name */
129         memset ((void *) &ai_hints, '\0', sizeof (ai_hints));
130         ai_hints.ai_family   = AF_INET; /* XXX: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */
131         ai_hints.ai_socktype = SOCK_STREAM;
132
133         status = getaddrinfo (host, port_str, &ai_hints, &ai_return);
134         if (status != 0)
135         {
136                 char errbuf[1024];
137                 DEBUG ("getaddrinfo failed: %s",
138                                 (status == EAI_SYSTEM)
139                                 ? sstrerror (errno, errbuf, sizeof (errbuf))
140                                 : gai_strerror (status));
141                 return (-1);
142         }
143
144         /* Create socket */
145         sd = -1;
146         for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next)
147         {
148                 sd = socket (ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol);
149                 if (sd >= 0)
150                         break;
151         }
152         /* `ai_list' still holds the current description of the socket.. */
153
154         if (sd < 0)
155         {
156                 DEBUG ("Unable to open a socket");
157                 freeaddrinfo (ai_return);
158                 return (-1);
159         }
160
161         status = connect (sd, ai_list->ai_addr, ai_list->ai_addrlen);
162
163         freeaddrinfo (ai_return);
164
165         if (status != 0) /* `connect(2)' failed */
166         {
167                 char errbuf[1024];
168                 DEBUG ("connect failed: %s",
169                                 sstrerror (errno, errbuf, sizeof (errbuf)));
170                 close (sd);
171                 return (-1);
172         }
173
174         DEBUG ("Done opening a socket %i", sd);
175
176         return (sd);
177 } /* int net_open (char *host, char *service, int port) */
178
179 /* 
180  * Receive a message from the other end. Each message consists of
181  * two packets. The first is a header that contains the size
182  * of the data that follows in the second packet.
183  * Returns number of bytes read
184  * Returns 0 on end of file
185  * Returns -1 on hard end of file (i.e. network connection close)
186  * Returns -2 on error
187  */
188 static int net_recv (int *sockfd, char *buf, int buflen)
189 {
190         uint16_t packet_size;
191
192         /* get data size -- in short */
193         if (sread (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
194         {
195                 *sockfd = -1;
196                 return (-1);
197         }
198
199         packet_size = ntohs (packet_size);
200         if (packet_size > buflen)
201         {
202                 DEBUG ("record length too large");
203                 return (-2);
204         }
205
206         if (packet_size == 0)
207                 return (0);
208
209         /* now read the actual data */
210         if (sread (*sockfd, (void *) buf, packet_size) != 0)
211         {
212                 *sockfd = -1;
213                 return (-1);
214         }
215
216         return ((int) packet_size);
217 } /* static int net_recv (int *sockfd, char *buf, int buflen) */
218
219 /*
220  * Send a message over the network. The send consists of
221  * two network packets. The first is sends a short containing
222  * the length of the data packet which follows.
223  * Returns zero on success
224  * Returns non-zero on error
225  */
226 static int net_send (int *sockfd, char *buff, int len)
227 {
228         uint16_t packet_size;
229
230         assert (len > 0);
231         assert (*sockfd >= 0);
232
233         /* send short containing size of data packet */
234         packet_size = htons ((uint16_t) len);
235
236         if (swrite (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
237         {
238                 *sockfd = -1;
239                 return (-1);
240         }
241
242         /* send data packet */
243         if (swrite (*sockfd, (void *) buff, len) != 0)
244         {
245                 *sockfd = -1;
246                 return (-2);
247         }
248
249         return (0);
250 }
251
252 /* Get and print status from apcupsd NIS server */
253 static int apc_query_server (char *host, int port,
254                 struct apc_detail_s *apcups_detail)
255 {
256         int     n;
257         char    recvline[1024];
258         char   *tokptr;
259         char   *toksaveptr;
260         char   *key;
261         double  value;
262
263         static complain_t compl;
264
265 #if APCMAIN
266 # define PRINT_VALUE(name, val) printf("  Found property: name = %s; value = %f;\n", name, val)
267 #else
268 # define PRINT_VALUE(name, val) /**/
269 #endif
270
271         if (global_sockfd < 0)
272         {
273                 if ((global_sockfd = net_open (host, NULL, port)) < 0)
274                 {
275                         plugin_complain (LOG_ERR, &compl, "apcups plugin: "
276                                         "Connecting to the apcupsd failed.");
277                         return (-1);
278                 }
279                 else
280                 {
281                         plugin_relief (LOG_NOTICE, &compl, "apcups plugin: "
282                                         "Connection re-established to the apcupsd.");
283                 }
284         }
285
286         if (net_send (&global_sockfd, "status", 6) < 0)
287         {
288                 ERROR ("apcups plugin: Writing to the socket failed.");
289                 return (-1);
290         }
291
292         while ((n = net_recv (&global_sockfd, recvline, sizeof (recvline) - 1)) > 0)
293         {
294                 assert (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                 {
304                         key = tokptr;
305                         if ((tokptr = strtok_r (NULL, " :\t", &toksaveptr)) == NULL)
306                                 continue;
307                         value = atof (tokptr);
308
309                         PRINT_VALUE (key, value);
310
311                         if (strcmp ("LINEV", key) == 0)
312                                 apcups_detail->linev = value;
313                         else if (strcmp ("BATTV", key) == 0) 
314                                 apcups_detail->battv = value;
315                         else if (strcmp ("ITEMP", key) == 0)
316                                 apcups_detail->itemp = value;
317                         else if (strcmp ("LOADPCT", key) == 0)
318                                 apcups_detail->loadpct = value;
319                         else if (strcmp ("BCHARGE", key) == 0)
320                                 apcups_detail->bcharge = value;
321                         else if (strcmp ("OUTPUTV", key) == 0)
322                                 apcups_detail->outputv = value;
323                         else if (strcmp ("LINEFREQ", key) == 0)
324                                 apcups_detail->linefreq = value;
325                         else if (strcmp ("TIMELEFT", key) == 0)
326                                 apcups_detail->timeleft = value;
327
328                         tokptr = strtok_r (NULL, ":", &toksaveptr);
329                 } /* while (tokptr != NULL) */
330         }
331         
332         if (n < 0)
333         {
334                 WARNING ("apcups plugin: Error reading from socket");
335                 return (-1);
336         }
337
338         return (0);
339 }
340
341 static int apcups_config (const char *key, const char *value)
342 {
343         if (strcasecmp (key, "host") == 0)
344         {
345                 if (conf_host != NULL)
346                 {
347                         free (conf_host);
348                         conf_host = NULL;
349                 }
350                 if ((conf_host = strdup (value)) == NULL)
351                         return (1);
352         }
353         else if (strcasecmp (key, "Port") == 0)
354         {
355                 int port_tmp = atoi (value);
356                 if (port_tmp < 1 || port_tmp > 65535)
357                 {
358                         WARNING ("apcups plugin: Invalid port: %i", port_tmp);
359                         return (1);
360                 }
361                 conf_port = port_tmp;
362         }
363         else
364         {
365                 return (-1);
366         }
367         return (0);
368 }
369
370 static void apc_submit_generic (char *type, char *type_inst, double value)
371 {
372         value_t values[1];
373         value_list_t vl = VALUE_LIST_INIT;
374
375         values[0].gauge = value;
376
377         vl.values = values;
378         vl.values_len = 1;
379         vl.time = time (NULL);
380         strcpy (vl.host, hostname_g);
381         strcpy (vl.plugin, "apcups");
382         strcpy (vl.plugin_instance, "");
383         strncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
384
385         plugin_dispatch_values (type, &vl);
386 }
387
388 static void apc_submit (struct apc_detail_s *apcups_detail)
389 {
390         apc_submit_generic ("apcups_voltage",    "input",   apcups_detail->linev);
391         apc_submit_generic ("apcups_voltage",    "output",  apcups_detail->outputv);
392         apc_submit_generic ("apcups_voltage",    "battery", apcups_detail->battv);
393         apc_submit_generic ("apcups_charge",     "",        apcups_detail->bcharge);
394         apc_submit_generic ("apcups_charge_pct", "",        apcups_detail->loadpct);
395         apc_submit_generic ("apcups_timeleft",   "",        apcups_detail->timeleft);
396         apc_submit_generic ("apcups_temp",       "",        apcups_detail->itemp);
397         apc_submit_generic ("apcups_frequency",  "input",   apcups_detail->linefreq);
398 }
399
400 static int apcups_read (void)
401 {
402         struct apc_detail_s apcups_detail;
403         int status;
404
405         apcups_detail.linev    =   -1.0;
406         apcups_detail.outputv  =   -1.0;
407         apcups_detail.battv    =   -1.0;
408         apcups_detail.loadpct  =   -1.0;
409         apcups_detail.bcharge  =   -1.0;
410         apcups_detail.timeleft =   -1.0;
411         apcups_detail.itemp    = -300.0;
412         apcups_detail.linefreq =   -1.0;
413   
414         status = apc_query_server (conf_host == NULL
415                         ? APCUPS_DEFAULT_HOST
416                         : conf_host,
417                         conf_port, &apcups_detail);
418  
419         /*
420          * if we did not connect then do not bother submitting
421          * zeros. We want rrd files to have NAN.
422          */
423         if (status != 0)
424         {
425                 DEBUG ("apc_query_server (%s, %i) = %i",
426                                 conf_host == NULL
427                                 ? APCUPS_DEFAULT_HOST
428                                 : conf_host,
429                                 conf_port, status);
430                 return (-1);
431         }
432
433         apc_submit (&apcups_detail);
434
435         return (0);
436 } /* apcups_read */
437
438 void module_register (void)
439 {
440         plugin_register_config ("apcups", apcups_config, config_keys,
441                         config_keys_num);
442         plugin_register_read ("apcups", apcups_read);
443         plugin_register_shutdown ("apcups", apcups_shutdown);
444 } /* void module_register */