Code

Renamed `host' and `port' to `global_host' and `global_port' to distinguish between...
[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  **/
25 #include "collectd.h"
26 #include "common.h" /* rrd_update_file */
27 #include "plugin.h" /* plugin_register, plugin_submit */
28 #include "configfile.h" /* cf_register */
29 #include "utils_debug.h"
31 #if HAVE_SYS_TYPES_H
32 # include <sys/types.h>
33 #endif
34 #if HAVE_SYS_SOCKET_H
35 # include <sys/socket.h>
36 #endif
37 #if HAVE_NETDB_H
38 # include <netdb.h>
39 #endif
40 #if HAVE_NETINET_IN_H
41 # include <netinet/in.h>
42 #endif
44 #if 0
45 #if HAVE_ARPA_INET_H
46 # include <arpa/inet.h> /* inet_addr */
47 #endif
48 #include <pwd.h>
49 #include <setjmp.h> /* FIXME: Is this really neccessary? */
50 #include <termios.h> /* FIXME: Is this really neccessary? */
51 #include <sys/ioctl.h> /* FIXME: Is this really neccessary? */
52 #include <sys/ipc.h> /* FIXME: Is this really neccessary? */
53 #include <sys/sem.h> /* FIXME: Is this really neccessary? */
54 #include <sys/shm.h> /* FIXME: Is this really neccessary? */
55 #endif
57 #define NISPORT 3551
58 #define _(String) (String)
59 #define N_(String) (String)
60 #define MAXSTRING               256
61 #define MODULE_NAME "apcups"
63 /* Default values for contacting daemon */
64 static char *global_host = NULL;
65 static int   global_port = NISPORT;
67 /* 
68  * The following are only if not compiled to test the module with its own main.
69 */
70 /* FIXME: Rename DSes to be more generic and follow established conventions. */
71 #ifndef APCMAIN
72 static char *bvolt_file_template = "apcups/voltage-%s.rrd";
73 static char *bvolt_ds_def[] = 
74 {
75         "DS:voltage:GAUGE:"COLLECTD_HEARTBEAT":0:U",
76 };
77 static int bvolt_ds_num = 1;
79 static char *load_file_template = "apcups/charge_percent.rrd";
80 static char *load_ds_def[] = 
81 {
82         "DS:percent:GAUGE:"COLLECTD_HEARTBEAT":0:100",
83 };
84 static int load_ds_num = 1;
86 static char *charge_file_template = "apcups/charge.rrd";
87 static char *charge_ds_def[] = 
88 {
89         "DS:charge:GAUGE:"COLLECTD_HEARTBEAT":0:U",
90 };
91 static int charge_ds_num = 1;
93 static char *time_file_template = "apcups/time.rrd";
94 static char *time_ds_def[] = 
95 {
96         "DS:timeleft:GAUGE:"COLLECTD_HEARTBEAT":0:100",
97 };
98 static int time_ds_num = 1;
100 static char *temp_file_template = "apcups/temperature.rrd";
101 static char *temp_ds_def[] = 
103         /* -273.15 is absolute zero */
104         "DS:temperature:GAUGE:"COLLECTD_HEARTBEAT":-274:U",
105 };
106 static int temp_ds_num = 1;
108 static char *freq_file_template = "apcups/frequency-%s.rrd";
109 static char *freq_ds_def[] = 
111         "DS:frequency:GAUGE:"COLLECTD_HEARTBEAT":0:U",
112 };
113 static int freq_ds_num = 1;
115 static char *config_keys[] =
117         "Host",
118         "Port",
119         NULL
120 };
121 static int config_keys_num = 2;
123 #endif /* ifndef APCMAIN */
125 struct apc_detail_s
127         double linev;
128         double loadpct;
129         double bcharge;
130         double timeleft;
131         double outputv;
132         double itemp;
133         double battv;
134         double linefreq;
135 };
137 #define BIG_BUF 4096
139 /*
140  * Read nbytes from the network.
141  * It is possible that the total bytes require in several
142  * read requests
143  */
144 static int read_nbytes (int fd, char *ptr, int nbytes)
146         int nleft, nread;
148         nleft = nbytes;
150         while (nleft > 0) {
151                 do {
152                         nread = read (fd, ptr, nleft);
153                 } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
155                 if (nread <= 0) {
156                         return (nread);           /* error, or EOF */
157                 }
159                 nleft -= nread;
160                 ptr += nread;
161         }
163         return (nbytes - nleft);        /* return >= 0 */
166 /*
167  * Write nbytes to the network.
168  * It may require several writes.
169  */
170 static int write_nbytes (int fd, void *buf, int buflen)
172         int nleft;
173         int nwritten;
174         char *ptr;
176         ptr = (char *) buf;
178         nleft = buflen;
179         while (nleft > 0)
180         {
181                 nwritten = write (fd, ptr, nleft);
183                 if (nwritten <= 0)
184                 {
185                         syslog (LOG_ERR, "Writing to socket failed: %s", strerror (errno));
186                         return (nwritten);
187                 }
189                 nleft -= nwritten;
190                 ptr += nwritten;
191         }
193         /* If we get here, (nleft <= 0) is true */
194         return (buflen);
197 /* Close the network connection */
198 static void net_close (int sockfd)
200         short pktsiz = 0;
202         /* send EOF sentinel */
203         write_nbytes (sockfd, &pktsiz, sizeof (short));
204         close (sockfd);
208 /*     
209  * Open a TCP connection to the UPS network server
210  * Returns -1 on error
211  * Returns socket file descriptor otherwise
212  */
213 static int net_open (char *host, char *service, int port)
215         int              sd;
216         int              status;
217         char             port_str[8];
218         struct addrinfo  ai_hints;
219         struct addrinfo *ai_return;
220         struct addrinfo *ai_list;
222         assert ((port > 0x00000000) && (port <= 0x0000FFFF));
224         /* Convert the port to a string */
225         snprintf (port_str, 8, "%i", port);
226         port_str[7] = '\0';
228         /* Resolve name */
229         memset ((void *) &ai_hints, '\0', sizeof (ai_hints));
230         ai_hints.ai_family   = AF_INET; /* XXX: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */
231         ai_hints.ai_socktype = SOCK_STREAM;
233         status = getaddrinfo (host, port_str, &ai_hints, &ai_return);
234         if (status != 0)
235         {
236                 DBG ("getaddrinfo failed: %s", status == EAI_SYSTEM ? strerror (errno) : gai_strerror (status));
237                 return (-1);
238         }
240         /* Create socket */
241         sd = -1;
242         for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next)
243         {
244                 sd = socket (ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol);
245                 if (sd >= 0)
246                         break;
247         }
248         /* `ai_list' still holds the current description of the socket.. */
250         if (sd < 0)
251         {
252                 DBG ("Unable to open a socket");
253                 freeaddrinfo (ai_return);
254                 return (-1);
255         }
257         status = connect (sd, ai_list->ai_addr, ai_list->ai_addrlen);
259         freeaddrinfo (ai_return);
261         if (status != 0) /* `connect(2)' failed */
262         {
263                 DBG ("connect failed: %s", strerror (errno));
264                 return (-1);
265         }
267         return (sd);
268 } /* int net_open (char *host, char *service, int port) */
270 /* 
271  * Receive a message from the other end. Each message consists of
272  * two packets. The first is a header that contains the size
273  * of the data that follows in the second packet.
274  * Returns number of bytes read
275  * Returns 0 on end of file
276  * Returns -1 on hard end of file (i.e. network connection close)
277  * Returns -2 on error
278  */
279 static int net_recv (int sockfd, char *buf, int buflen)
281         int   nbytes;
282         short pktsiz;
284         /* get data size -- in short */
285         if ((nbytes = read_nbytes (sockfd, (char *) &pktsiz, sizeof (short))) <= 0)
286                 return (-1);
288         if (nbytes != sizeof (short))
289                 return (-2);
291         pktsiz = ntohs (pktsiz);
292         if (pktsiz > buflen)
293         {
294                 DBG ("record length too large");
295                 return (-2);
296         }
298         if (pktsiz == 0)
299                 return (0);
301         /* now read the actual data */
302         if ((nbytes = read_nbytes (sockfd, buf, pktsiz)) <= 0)
303                 return (-2);
305         if (nbytes != pktsiz)
306                 return (-2);
308         return (nbytes);
309 } /* static int net_recv (int sockfd, char *buf, int buflen) */
311 /*
312  * Send a message over the network. The send consists of
313  * two network packets. The first is sends a short containing
314  * the length of the data packet which follows.
315  * Returns zero on success
316  * Returns non-zero on error
317  */
318 static int net_send (int sockfd, char *buff, int len)
320         int rc;
321         short packet_size;
323         /* send short containing size of data packet */
324         packet_size = htons ((short) len);
326         rc = write_nbytes (sockfd, &packet_size, sizeof (packet_size));
327         if (rc != sizeof (packet_size))
328                 return (-1);
330         /* send data packet */
331         rc = write_nbytes (sockfd, buff, len);
332         if (rc != len)
333                 return (-1);
335         return (0);
338 /* Get and print status from apcupsd NIS server */
339 static int apc_query_server (char *host, int port,
340                 struct apc_detail_s *apcups_detail)
342         int     sockfd;
343         int     n;
344         char    recvline[MAXSTRING + 1];
345         char   *tokptr;
346         char   *key;
347         double  value;
348 #if APCMAIN
349 # define PRINT_VALUE(name, val) printf("  Found property: name = %s; value = %f;\n", name, val)
350 #else
351 # define PRINT_VALUE(name, val) /**/
352 #endif
354         /* TODO: Keep the socket open, if possible */
355         if ((sockfd = net_open (host, NULL, port)) < 0)
356         {
357                 syslog (LOG_ERR, "apcups plugin: Connecting to the apcupsd failed.");
358                 return (-1);
359         }
361         net_send (sockfd, "status", 6);
363         while ((n = net_recv (sockfd, recvline, sizeof (recvline))) > 0)
364         {
365                 recvline[n] = '\0';
366 #if APCMAIN
367                 printf ("net_recv = `%s';\n", recvline);
368 #endif /* if APCMAIN */
370                 tokptr = strtok (recvline, ":");
371                 while (tokptr != NULL)
372                 {
373                         key = tokptr;
374                         if ((tokptr = strtok (NULL, " \t")) == NULL)
375                                 continue;
376                         value = atof (tokptr);
377                         PRINT_VALUE (key, value);
379                         if (strcmp ("LINEV", key) == 0)
380                                 apcups_detail->linev = value;
381                         else if (strcmp ("BATTV", tokptr) == 0)
382                                 apcups_detail->battv = value;
383                         else if (strcmp ("ITEMP", tokptr) == 0)
384                                 apcups_detail->itemp = value;
385                         else if (strcmp ("LOADPCT", tokptr) == 0)
386                                 apcups_detail->loadpct = value;
387                         else if (strcmp ("BCHARGE", tokptr) == 0)
388                                 apcups_detail->bcharge = value;
389                         else if (strcmp ("OUTPUTV", tokptr) == 0)
390                                 apcups_detail->outputv = value;
391                         else if (strcmp ("LINEFREQ", tokptr) == 0)
392                                 apcups_detail->linefreq = value;
393                         else if (strcmp ("TIMELEFT", tokptr) == 0)
394                                 apcups_detail->timeleft = value;
395                         else
396                         {
397                                 syslog (LOG_WARNING, "apcups plugin: Received unknown property from apcupsd: `%s' = %f",
398                                                 key, value);
399                         }
401                         tokptr = strtok (NULL, ":");
402                 } /* while (tokptr != NULL) */
403         }
405         net_close (sockfd);
407         if (n < 0)
408         {
409                 syslog (LOG_WARNING, "apcups plugin: Error reading from socket");
410                 return (-1);
411         }
413         return (0);
416 #ifdef APCMAIN
417 int main (int argc, char **argv)
419         /* we are not really going to use this */
420         struct apc_detail_s apcups_detail;
422         if (!*host || strcmp (host, "0.0.0.0") == 0)
423                 host = "localhost";
425         apc_query_server (global_host, global_port, &apcups_detail);
427         return 0;
429 #else
430 static int apcups_config (char *key, char *value)
432         if (strcasecmp (key, "host") == 0)
433         {
434                 if (global_host != NULL)
435                 {
436                         free (global_host);
437                         global_host = NULL;
438                 }
439                 if ((global_host = strdup (value)) == NULL)
440                         return (1);
441         }
442         else if (strcasecmp (key, "Port") == 0)
443         {
444                 int port_tmp = atoi (value);
445                 if (port_tmp < 1 || port_tmp > 65535)
446                 {
447                         syslog (LOG_WARNING, "apcups plugin: Invalid port: %i", port_tmp);
448                         return (1);
449                 }
450                 global_port = port_tmp;
451         }
452         else
453         {
454                 return (-1);
455         }
456         return (0);
459 static void apcups_init (void)
461         return;
464 static void apc_write_voltage (char *host, char *inst, char *val)
466         char file[512];
467         int  status;
469         status = snprintf (file, 512, bvolt_file_template, inst);
470         if ((status < 1) || (status >= 512))
471                 return;
473         rrd_update_file (host, file, val, bvolt_ds_def, bvolt_ds_num);
476 static void apc_write_charge (char *host, char *inst, char *val)
478         rrd_update_file (host, charge_file_template, val, charge_ds_def, charge_ds_num);
481 static void apc_write_percent (char *host, char *inst, char *val)
483         rrd_update_file (host, load_file_template, val, load_ds_def, load_ds_num);
486 static void apc_write_timeleft (char *host, char *inst, char *val)
488         rrd_update_file (host, time_file_template, val, time_ds_def, time_ds_num);
491 static void apc_write_temperature (char *host, char *inst, char *val)
493         rrd_update_file (host, temp_file_template, val, temp_ds_def, temp_ds_num);
496 static void apc_write_frequency (char *host, char *inst, char *val)
498         char file[512];
499         int  status;
501         status = snprintf (file, 512, freq_file_template, inst);
502         if ((status < 1) || (status >= 512))
503                 return;
505         rrd_update_file (host, file, val, freq_ds_def, freq_ds_num);
508 static void apc_submit_generic (char *type, char *inst,
509                 double value)
511         char buf[512];
512         int  status;
514         status = snprintf (buf, 512, "%u:%f",
515                         (unsigned int) curtime, value);
516         if ((status < 1) || (status >= 512))
517                 return;
519         plugin_submit (type, inst, buf);
522 static void apc_submit (struct apc_detail_s *apcups_detail)
524         apc_submit_generic ("apcups_voltage",    "input",   apcups_detail->linev);
525         apc_submit_generic ("apcups_voltage",    "output",  apcups_detail->outputv);
526         apc_submit_generic ("apcups_voltage",    "battery", apcups_detail->battv);
527         apc_submit_generic ("apcups_charge",     "-",       apcups_detail->bcharge);
528         apc_submit_generic ("apcups_charge_pct", "-",       apcups_detail->loadpct);
529         apc_submit_generic ("apcups_timeleft",   "-",       apcups_detail->timeleft);
530         apc_submit_generic ("apcups_temp",       "-",       apcups_detail->itemp);
531         apc_submit_generic ("apcups_frequency",  "input",   apcups_detail->linefreq);
534 static void apcups_read (void)
536         struct apc_detail_s apcups_detail;
537         int status;
539         if (global_host == NULL)
540                 return;
541         
542         apcups_detail.linev    =   -1.0;
543         apcups_detail.outputv  =   -1.0;
544         apcups_detail.battv    =   -1.0;
545         apcups_detail.loadpct  =   -1.0;
546         apcups_detail.bcharge  =   -1.0;
547         apcups_detail.timeleft =   -1.0;
548         apcups_detail.itemp    = -300.0;
549         apcups_detail.linefreq =   -1.0;
550   
551         status = apc_query_server (global_host, global_port, &apcups_detail);
552  
553         /*
554          * if we did not connect then do not bother submitting
555          * zeros. We want rrd files to have NAN.
556          */
557         if (status != 0)
558                 return;
560         apc_submit (&apcups_detail);
561 } /* apcups_read */
563 void module_register (void)
565         plugin_register (MODULE_NAME, apcups_init, apcups_read, NULL);
566         plugin_register ("apcups_voltage",    NULL, NULL, apc_write_voltage);
567         plugin_register ("apcups_charge",     NULL, NULL, apc_write_charge);
568         plugin_register ("apcups_charge_pct", NULL, NULL, apc_write_percent);
569         plugin_register ("apcups_timeleft",   NULL, NULL, apc_write_timeleft);
570         plugin_register ("apcups_temp",       NULL, NULL, apc_write_temperature);
571         plugin_register ("apcups_frequency",  NULL, NULL, apc_write_frequency);
572         cf_register (MODULE_NAME, apcups_config, config_keys, config_keys_num);
575 #endif /* ifdef APCMAIN */
576 #undef MODULE_NAME