unixsock plugin: Fix the initialization of the pthread variable under Mac OS X.
[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 #include "utils_debug.h"
37
38 #if HAVE_SYS_TYPES_H
39 # include <sys/types.h>
40 #endif
41 #if HAVE_SYS_SOCKET_H
42 # include <sys/socket.h>
43 #endif
44 #if HAVE_NETDB_H
45 # include <netdb.h>
46 #endif
47
48 #if HAVE_NETINET_IN_H
49 # include <netinet/in.h>
50 #endif
51
52 #define NISPORT 3551
53 #define MAXSTRING               256
54 #define MODULE_NAME "apcups"
55
56 #define APCUPS_DEFAULT_HOST "localhost"
57
58 /*
59  * Private data types
60  */
61 struct apc_detail_s
62 {
63         double linev;
64         double loadpct;
65         double bcharge;
66         double timeleft;
67         double outputv;
68         double itemp;
69         double battv;
70         double linefreq;
71 };
72
73 /*
74  * Private variables
75  */
76 /* Default values for contacting daemon */
77 static char *conf_host = NULL;
78 static int   conf_port = NISPORT;
79
80 static int global_sockfd = -1;
81
82 /* 
83  * The following are only if not compiled to test the module with its own main.
84 */
85 static data_source_t data_source_voltage[1] =
86 {
87         {"voltage", DS_TYPE_GAUGE, NAN, NAN}
88 };
89
90 static data_set_t ds_voltage =
91 {
92         "voltage", 1, data_source_voltage
93 };
94
95 static data_source_t data_source_percent[1] =
96 {
97         {"percent", DS_TYPE_GAUGE, 0, 100.1}
98 };
99
100 static data_set_t ds_percent =
101 {
102         "percent", 1, data_source_percent
103 };
104
105 static data_source_t data_source_timeleft[1] =
106 {
107         {"timeleft", DS_TYPE_GAUGE, 0, 100.0}
108 };
109
110 static data_set_t ds_timeleft =
111 {
112         "timeleft", 1, data_source_timeleft
113 };
114
115 static data_source_t data_source_temperature[1] =
116 {
117         {"value", DS_TYPE_GAUGE, -273.15, NAN}
118 };
119
120 static data_set_t ds_temperature =
121 {
122         "temperature", 1, data_source_temperature
123 };
124
125 static data_source_t data_source_frequency[1] =
126 {
127         {"frequency", DS_TYPE_GAUGE, 0, NAN}
128 };
129
130 static data_set_t ds_frequency =
131 {
132         "frequency", 1, data_source_frequency
133 };
134
135 static const char *config_keys[] =
136 {
137         "Host",
138         "Port",
139         NULL
140 };
141 static int config_keys_num = 2;
142
143 /* Close the network connection */
144 static int apcups_shutdown (void)
145 {
146         uint16_t packet_size = 0;
147
148         if (global_sockfd < 0)
149                 return (0);
150
151         DBG ("Gracefully shutting down socket %i.", global_sockfd);
152
153         /* send EOF sentinel */
154         swrite (global_sockfd, (void *) &packet_size, sizeof (packet_size));
155
156         close (global_sockfd);
157         global_sockfd = -1;
158
159         return (0);
160 } /* int apcups_shutdown */
161
162 /*     
163  * Open a TCP connection to the UPS network server
164  * Returns -1 on error
165  * Returns socket file descriptor otherwise
166  */
167 static int net_open (char *host, char *service, int port)
168 {
169         int              sd;
170         int              status;
171         char             port_str[8];
172         struct addrinfo  ai_hints;
173         struct addrinfo *ai_return;
174         struct addrinfo *ai_list;
175
176         assert ((port > 0x00000000) && (port <= 0x0000FFFF));
177
178         /* Convert the port to a string */
179         snprintf (port_str, 8, "%i", port);
180         port_str[7] = '\0';
181
182         /* Resolve name */
183         memset ((void *) &ai_hints, '\0', sizeof (ai_hints));
184         ai_hints.ai_family   = AF_INET; /* XXX: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */
185         ai_hints.ai_socktype = SOCK_STREAM;
186
187         status = getaddrinfo (host, port_str, &ai_hints, &ai_return);
188         if (status != 0)
189         {
190                 DBG ("getaddrinfo failed: %s", status == EAI_SYSTEM ? strerror (errno) : gai_strerror (status));
191                 return (-1);
192         }
193
194         /* Create socket */
195         sd = -1;
196         for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next)
197         {
198                 sd = socket (ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol);
199                 if (sd >= 0)
200                         break;
201         }
202         /* `ai_list' still holds the current description of the socket.. */
203
204         if (sd < 0)
205         {
206                 DBG ("Unable to open a socket");
207                 freeaddrinfo (ai_return);
208                 return (-1);
209         }
210
211         status = connect (sd, ai_list->ai_addr, ai_list->ai_addrlen);
212
213         freeaddrinfo (ai_return);
214
215         if (status != 0) /* `connect(2)' failed */
216         {
217                 DBG ("connect failed: %s", strerror (errno));
218                 close (sd);
219                 return (-1);
220         }
221
222         DBG ("Done opening a socket %i", sd);
223
224         return (sd);
225 } /* int net_open (char *host, char *service, int port) */
226
227 /* 
228  * Receive a message from the other end. Each message consists of
229  * two packets. The first is a header that contains the size
230  * of the data that follows in the second packet.
231  * Returns number of bytes read
232  * Returns 0 on end of file
233  * Returns -1 on hard end of file (i.e. network connection close)
234  * Returns -2 on error
235  */
236 static int net_recv (int *sockfd, char *buf, int buflen)
237 {
238         uint16_t packet_size;
239
240         /* get data size -- in short */
241         if (sread (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
242         {
243                 *sockfd = -1;
244                 return (-1);
245         }
246
247         packet_size = ntohs (packet_size);
248         if (packet_size > buflen)
249         {
250                 DBG ("record length too large");
251                 return (-2);
252         }
253
254         if (packet_size == 0)
255                 return (0);
256
257         /* now read the actual data */
258         if (sread (*sockfd, (void *) buf, packet_size) != 0)
259         {
260                 *sockfd = -1;
261                 return (-1);
262         }
263
264         return ((int) packet_size);
265 } /* static int net_recv (int *sockfd, char *buf, int buflen) */
266
267 /*
268  * Send a message over the network. The send consists of
269  * two network packets. The first is sends a short containing
270  * the length of the data packet which follows.
271  * Returns zero on success
272  * Returns non-zero on error
273  */
274 static int net_send (int *sockfd, char *buff, int len)
275 {
276         uint16_t packet_size;
277
278         assert (len > 0);
279         assert (*sockfd >= 0);
280
281         /* send short containing size of data packet */
282         packet_size = htons ((uint16_t) len);
283
284         if (swrite (*sockfd, (void *) &packet_size, sizeof (packet_size)) != 0)
285         {
286                 *sockfd = -1;
287                 return (-1);
288         }
289
290         /* send data packet */
291         if (swrite (*sockfd, (void *) buff, len) != 0)
292         {
293                 *sockfd = -1;
294                 return (-2);
295         }
296
297         return (0);
298 }
299
300 /* Get and print status from apcupsd NIS server */
301 static int apc_query_server (char *host, int port,
302                 struct apc_detail_s *apcups_detail)
303 {
304         int     n;
305         char    recvline[1024];
306         char   *tokptr;
307         char   *key;
308         double  value;
309
310         static complain_t compl;
311
312 #if APCMAIN
313 # define PRINT_VALUE(name, val) printf("  Found property: name = %s; value = %f;\n", name, val)
314 #else
315 # define PRINT_VALUE(name, val) /**/
316 #endif
317
318         if (global_sockfd < 0)
319         {
320                 if ((global_sockfd = net_open (host, NULL, port)) < 0)
321                 {
322                         plugin_complain (LOG_ERR, &compl, "apcups plugin: "
323                                         "Connecting to the apcupsd failed.");
324                         return (-1);
325                 }
326                 else
327                 {
328                         plugin_relief (LOG_NOTICE, &compl, "apcups plugin: "
329                                         "Connection re-established to the apcupsd.");
330                 }
331         }
332
333         if (net_send (&global_sockfd, "status", 6) < 0)
334         {
335                 syslog (LOG_ERR, "apcups plugin: Writing to the socket failed.");
336                 return (-1);
337         }
338
339         while ((n = net_recv (&global_sockfd, recvline, sizeof (recvline) - 1)) > 0)
340         {
341                 assert (n < sizeof (recvline));
342                 recvline[n] = '\0';
343 #if APCMAIN
344                 printf ("net_recv = `%s';\n", recvline);
345 #endif /* if APCMAIN */
346
347                 tokptr = strtok (recvline, " :\t");
348                 while (tokptr != NULL)
349                 {
350                         key = tokptr;
351                         if ((tokptr = strtok (NULL, " :\t")) == NULL)
352                                 continue;
353                         value = atof (tokptr);
354
355                         PRINT_VALUE (key, value);
356
357                         if (strcmp ("LINEV", key) == 0)
358                                 apcups_detail->linev = value;
359                         else if (strcmp ("BATTV", key) == 0) 
360                                 apcups_detail->battv = value;
361                         else if (strcmp ("ITEMP", key) == 0)
362                                 apcups_detail->itemp = value;
363                         else if (strcmp ("LOADPCT", key) == 0)
364                                 apcups_detail->loadpct = value;
365                         else if (strcmp ("BCHARGE", key) == 0)
366                                 apcups_detail->bcharge = value;
367                         else if (strcmp ("OUTPUTV", key) == 0)
368                                 apcups_detail->outputv = value;
369                         else if (strcmp ("LINEFREQ", key) == 0)
370                                 apcups_detail->linefreq = value;
371                         else if (strcmp ("TIMELEFT", key) == 0)
372                                 apcups_detail->timeleft = value;
373
374                         tokptr = strtok (NULL, ":");
375                 } /* while (tokptr != NULL) */
376         }
377         
378         if (n < 0)
379         {
380                 syslog (LOG_WARNING, "apcups plugin: Error reading from socket");
381                 return (-1);
382         }
383
384         return (0);
385 }
386
387 static int apcups_config (const char *key, const char *value)
388 {
389         if (strcasecmp (key, "host") == 0)
390         {
391                 if (conf_host != NULL)
392                 {
393                         free (conf_host);
394                         conf_host = NULL;
395                 }
396                 if ((conf_host = strdup (value)) == NULL)
397                         return (1);
398         }
399         else if (strcasecmp (key, "Port") == 0)
400         {
401                 int port_tmp = atoi (value);
402                 if (port_tmp < 1 || port_tmp > 65535)
403                 {
404                         syslog (LOG_WARNING, "apcups plugin: Invalid port: %i", port_tmp);
405                         return (1);
406                 }
407                 conf_port = port_tmp;
408         }
409         else
410         {
411                 return (-1);
412         }
413         return (0);
414 }
415
416 static void apc_submit_generic (char *type, char *type_inst, double value)
417 {
418         value_t values[1];
419         value_list_t vl = VALUE_LIST_INIT;
420
421         values[0].gauge = value;
422
423         vl.values = values;
424         vl.values_len = 1;
425         vl.time = time (NULL);
426         strcpy (vl.host, hostname);
427         strcpy (vl.plugin, "apcups");
428         strcpy (vl.plugin_instance, "");
429         strncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
430
431         plugin_dispatch_values (type, &vl);
432 }
433
434 static void apc_submit (struct apc_detail_s *apcups_detail)
435 {
436         apc_submit_generic ("apcups_voltage",    "input",   apcups_detail->linev);
437         apc_submit_generic ("apcups_voltage",    "output",  apcups_detail->outputv);
438         apc_submit_generic ("apcups_voltage",    "battery", apcups_detail->battv);
439         apc_submit_generic ("apcups_charge",     "",        apcups_detail->bcharge);
440         apc_submit_generic ("apcups_charge_pct", "",        apcups_detail->loadpct);
441         apc_submit_generic ("apcups_timeleft",   "",        apcups_detail->timeleft);
442         apc_submit_generic ("apcups_temp",       "",        apcups_detail->itemp);
443         apc_submit_generic ("apcups_frequency",  "input",   apcups_detail->linefreq);
444 }
445
446 static int apcups_read (void)
447 {
448         struct apc_detail_s apcups_detail;
449         int status;
450
451         apcups_detail.linev    =   -1.0;
452         apcups_detail.outputv  =   -1.0;
453         apcups_detail.battv    =   -1.0;
454         apcups_detail.loadpct  =   -1.0;
455         apcups_detail.bcharge  =   -1.0;
456         apcups_detail.timeleft =   -1.0;
457         apcups_detail.itemp    = -300.0;
458         apcups_detail.linefreq =   -1.0;
459   
460         status = apc_query_server (conf_host == NULL
461                         ? APCUPS_DEFAULT_HOST
462                         : conf_host,
463                         conf_port, &apcups_detail);
464  
465         /*
466          * if we did not connect then do not bother submitting
467          * zeros. We want rrd files to have NAN.
468          */
469         if (status != 0)
470         {
471                 DBG ("apc_query_server (%s, %i) = %i",
472                                 conf_host == NULL
473                                 ? APCUPS_DEFAULT_HOST
474                                 : conf_host,
475                                 conf_port, status);
476                 return (-1);
477         }
478
479         apc_submit (&apcups_detail);
480
481         return (0);
482 } /* apcups_read */
483
484 void module_register (void)
485 {
486         plugin_register_data_set (&ds_voltage);
487         plugin_register_data_set (&ds_percent);
488         plugin_register_data_set (&ds_timeleft);
489         plugin_register_data_set (&ds_temperature);
490         plugin_register_data_set (&ds_frequency);
491
492         plugin_register_config ("apcups", apcups_config, config_keys, config_keys_num);
493
494         plugin_register_read ("apcups", apcups_read);
495         plugin_register_shutdown ("apcups", apcups_shutdown);
496 }