apcups branch: Some debug messages and such.
[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 #ifndef APCMAIN
53 # define APCMAIN 0
54 #endif
55
56 #define NISPORT 3551
57 #define MAXSTRING               256
58 #define MODULE_NAME "apcups"
59
60 #define APCUPS_DEFAULT_HOST "localhost"
61
62 /* Default values for contacting daemon */
63 static char *global_host = NULL;
64 static int   global_port = NISPORT;
65
66 /* 
67  * The following are only if not compiled to test the module with its own main.
68 */
69 #if !APCMAIN
70 static char *bvolt_file_template = "apcups/voltage-%s.rrd";
71 static char *bvolt_ds_def[] = 
72 {
73         "DS:voltage:GAUGE:"COLLECTD_HEARTBEAT":0:U",
74 };
75 static int bvolt_ds_num = 1;
76
77 static char *load_file_template = "apcups/load_percent.rrd";
78 static char *load_ds_def[] = 
79 {
80         "DS:percent:GAUGE:"COLLECTD_HEARTBEAT":0:110",
81 };
82 static int load_ds_num = 1;
83
84 static char *charge_file_template = "apcups/charge_percent.rrd";
85 static char *charge_ds_def[] = 
86 {
87         "DS:percent:GAUGE:"COLLECTD_HEARTBEAT":0:110",
88 };
89 static int charge_ds_num = 1;
90
91 static char *time_file_template = "apcups/timeleft.rrd";
92 static char *time_ds_def[] = 
93 {
94         "DS:timeleft:GAUGE:"COLLECTD_HEARTBEAT":0:100",
95 };
96 static int time_ds_num = 1;
97
98 static char *temp_file_template = "apcups/temperature.rrd";
99 static char *temp_ds_def[] = 
100 {
101         /* -273.15 is absolute zero */
102         "DS:value:GAUGE:"COLLECTD_HEARTBEAT":-274:U",
103 };
104 static int temp_ds_num = 1;
105
106 static char *freq_file_template = "apcups/frequency-%s.rrd";
107 static char *freq_ds_def[] = 
108 {
109         "DS:frequency:GAUGE:"COLLECTD_HEARTBEAT":0:U",
110 };
111 static int freq_ds_num = 1;
112
113 static char *config_keys[] =
114 {
115         "Host",
116         "Port",
117         NULL
118 };
119 static int config_keys_num = 2;
120
121 #endif /* if APCMAIN */
122
123 struct apc_detail_s
124 {
125         double linev;
126         double loadpct;
127         double bcharge;
128         double timeleft;
129         double outputv;
130         double itemp;
131         double battv;
132         double linefreq;
133 };
134
135 #define BIG_BUF 4096
136
137 /*
138  * Read nbytes from the network.
139  * It is possible that the total bytes require in several
140  * read requests
141  */
142 static int read_nbytes (int *fd, char *ptr, int nbytes)
143 {
144         int nleft;
145         int nread;
146
147         nleft = nbytes;
148         nread = -1;
149
150         assert (*fd >= 0);
151
152         while ((nleft > 0) && (nread != 0))
153         {
154                 nread = read (*fd, ptr, nleft);
155
156                 if ((nread < 0) && (errno == EINTR || errno == EAGAIN))
157                         continue;
158
159                 if (nread < 0)
160                 {
161                         *fd = -1;
162                         DBG ("Reading from socket failed failed: %s; *fd = -1;", strerror (errno));
163                         syslog (LOG_ERR, "apcups plugin: Reading from socket failed failed: %s", strerror (errno));
164                         return (-1);
165                 }
166
167                 if (nread == 0)
168                 {
169                         DBG ("Received EOF. Closing socket %i.", *fd);
170                         close (*fd);
171                         *fd = -1;
172                         return (nbytes - nleft);
173                 }
174
175                 nleft -= nread;
176                 ptr += nread;
177         }
178
179         return (nbytes - nleft);
180 }
181
182 /*
183  * Write nbytes to the network.
184  * It may require several writes.
185  */
186 static int write_nbytes (int *fd, void *buf, int buflen)
187 {
188         int nleft;
189         int nwritten;
190         char *ptr;
191
192         assert (buflen > 0);
193         assert (*fd >= 0);
194
195         ptr = (char *) buf;
196
197         nleft = buflen;
198         while (nleft > 0)
199         {
200                 nwritten = write (*fd, ptr, nleft);
201
202                 if ((nwritten < 0) && ((errno == EAGAIN) || (errno == EINTR)))
203                         continue;
204
205                 if (nwritten < 0)
206                 {
207                         *fd = -1;
208                         DBG ("Writing to socket failed: %s; *fd = -1;", strerror (errno));
209                         syslog (LOG_ERR, "apcups plugin: Writing to socket failed: %s", strerror (errno));
210                         return (-1);
211                 }
212
213                 nleft -= nwritten;
214                 ptr += nwritten;
215         }
216
217         /* If we get here, (nleft <= 0) is true */
218         return (buflen);
219 }
220
221 /* Close the network connection */
222 static void net_close (int *fd)
223 {
224         short pktsiz = 0;
225
226         assert (*fd >= 0);
227
228         DBG ("Gracefully shutting down socket %i.", *fd);
229
230         /* send EOF sentinel */
231         write_nbytes (fd, &pktsiz, sizeof (short));
232
233         close (*fd);
234         *fd = -1;
235 }
236
237 /*     
238  * Open a TCP connection to the UPS network server
239  * Returns -1 on error
240  * Returns socket file descriptor otherwise
241  */
242 static int net_open (char *host, char *service, int port)
243 {
244         int              sd;
245         int              status;
246         char             port_str[8];
247         struct addrinfo  ai_hints;
248         struct addrinfo *ai_return;
249         struct addrinfo *ai_list;
250
251         assert ((port > 0x00000000) && (port <= 0x0000FFFF));
252
253         /* Convert the port to a string */
254         snprintf (port_str, 8, "%i", port);
255         port_str[7] = '\0';
256
257         /* Resolve name */
258         memset ((void *) &ai_hints, '\0', sizeof (ai_hints));
259         ai_hints.ai_family   = AF_INET; /* XXX: Change this to `AF_UNSPEC' if apcupsd can handle IPv6 */
260         ai_hints.ai_socktype = SOCK_STREAM;
261
262         status = getaddrinfo (host, port_str, &ai_hints, &ai_return);
263         if (status != 0)
264         {
265                 DBG ("getaddrinfo failed: %s", status == EAI_SYSTEM ? strerror (errno) : gai_strerror (status));
266                 return (-1);
267         }
268
269         /* Create socket */
270         sd = -1;
271         for (ai_list = ai_return; ai_list != NULL; ai_list = ai_list->ai_next)
272         {
273                 sd = socket (ai_list->ai_family, ai_list->ai_socktype, ai_list->ai_protocol);
274                 if (sd >= 0)
275                         break;
276         }
277         /* `ai_list' still holds the current description of the socket.. */
278
279         if (sd < 0)
280         {
281                 DBG ("Unable to open a socket");
282                 freeaddrinfo (ai_return);
283                 return (-1);
284         }
285
286         status = connect (sd, ai_list->ai_addr, ai_list->ai_addrlen);
287
288         freeaddrinfo (ai_return);
289
290         if (status != 0) /* `connect(2)' failed */
291         {
292                 DBG ("connect failed: %s", strerror (errno));
293                 return (-1);
294         }
295
296         DBG ("Done opening a socket %i", sd);
297
298         return (sd);
299 } /* int net_open (char *host, char *service, int port) */
300
301 /* 
302  * Receive a message from the other end. Each message consists of
303  * two packets. The first is a header that contains the size
304  * of the data that follows in the second packet.
305  * Returns number of bytes read
306  * Returns 0 on end of file
307  * Returns -1 on hard end of file (i.e. network connection close)
308  * Returns -2 on error
309  */
310 static int net_recv (int *sockfd, char *buf, int buflen)
311 {
312         int   nbytes;
313         short pktsiz;
314
315         /* get data size -- in short */
316         if ((nbytes = read_nbytes (sockfd, (char *) &pktsiz, sizeof (short))) <= 0)
317                 return (-1);
318
319         if (nbytes != sizeof (short))
320                 return (-2);
321
322         pktsiz = ntohs (pktsiz);
323         if (pktsiz > buflen)
324         {
325                 DBG ("record length too large");
326                 return (-2);
327         }
328
329         if (pktsiz == 0)
330                 return (0);
331
332         /* now read the actual data */
333         if ((nbytes = read_nbytes (sockfd, buf, pktsiz)) <= 0)
334                 return (-2);
335
336         if (nbytes != pktsiz)
337                 return (-2);
338
339         return (nbytes);
340 } /* static int net_recv (int sockfd, char *buf, int buflen) */
341
342 /*
343  * Send a message over the network. The send consists of
344  * two network packets. The first is sends a short containing
345  * the length of the data packet which follows.
346  * Returns zero on success
347  * Returns non-zero on error
348  */
349 static int net_send (int *sockfd, char *buff, int len)
350 {
351         int rc;
352         short packet_size;
353
354         assert (len > 0);
355         assert (*sockfd >= 0);
356
357         /* send short containing size of data packet */
358         packet_size = htons ((short) len);
359
360         rc = write_nbytes (sockfd, &packet_size, sizeof (packet_size));
361         if (rc != sizeof (packet_size))
362                 return (-1);
363
364         /* send data packet */
365         rc = write_nbytes (sockfd, buff, len);
366         if (rc != len)
367                 return (-1);
368
369         return (0);
370 }
371
372 /* Get and print status from apcupsd NIS server */
373 static int apc_query_server (char *host, int port,
374                 struct apc_detail_s *apcups_detail)
375 {
376         int     n;
377         char    recvline[1024];
378         char   *tokptr;
379         char   *key;
380         double  value;
381
382         static int sockfd   = -1;
383         static unsigned int complain = 0;
384
385 #if APCMAIN
386 # define PRINT_VALUE(name, val) printf("  Found property: name = %s; value = %f;\n", name, val)
387 #else
388 # define PRINT_VALUE(name, val) /**/
389 #endif
390
391         if (sockfd < 0)
392         {
393                 if ((sockfd = net_open (host, NULL, port)) < 0)
394                 {
395                         /* Complain once every six hours. */
396                         int complain_step = 21600 / atoi (COLLECTD_STEP);
397
398                         if ((complain % complain_step) == 0)
399                                 syslog (LOG_ERR, "apcups plugin: Connecting to the apcupsd failed.");
400                         complain++;
401
402                         return (-1);
403                 }
404                 else if (complain > 1)
405                 {
406                         syslog (LOG_NOTICE, "apcups plugin: Connection re-established to the apcupsd.");
407                         complain = 0;
408                 }
409         }
410
411         if (net_send (&sockfd, "status", 6) < 0)
412         {
413                 syslog (LOG_ERR, "apcups plugin: Writing to the socket failed.");
414                 return (-1);
415         }
416
417         while ((n = net_recv (&sockfd, recvline, sizeof (recvline) - 1)) > 0)
418         {
419                 assert (n < sizeof (recvline));
420                 recvline[n] = '\0';
421 #if APCMAIN
422                 printf ("net_recv = `%s';\n", recvline);
423 #endif /* if APCMAIN */
424
425                 tokptr = strtok (recvline, " :\t");
426                 while (tokptr != NULL)
427                 {
428                         key = tokptr;
429                         if ((tokptr = strtok (NULL, " :\t")) == NULL)
430                                 continue;
431                         value = atof (tokptr);
432
433                         PRINT_VALUE (key, value);
434
435                         if (strcmp ("LINEV", key) == 0)
436                                 apcups_detail->linev = value;
437                         else if (strcmp ("BATTV", key) == 0) 
438                                 apcups_detail->battv = value;
439                         else if (strcmp ("ITEMP", key) == 0)
440                                 apcups_detail->itemp = value;
441                         else if (strcmp ("LOADPCT", key) == 0)
442                                 apcups_detail->loadpct = value;
443                         else if (strcmp ("BCHARGE", key) == 0)
444                                 apcups_detail->bcharge = value;
445                         else if (strcmp ("OUTPUTV", key) == 0)
446                                 apcups_detail->outputv = value;
447                         else if (strcmp ("LINEFREQ", key) == 0)
448                                 apcups_detail->linefreq = value;
449                         else if (strcmp ("TIMELEFT", key) == 0)
450                                 apcups_detail->timeleft = value;
451
452                         tokptr = strtok (NULL, ":");
453                 } /* while (tokptr != NULL) */
454         }
455         
456         if (n < 0)
457         {
458                 syslog (LOG_WARNING, "apcups plugin: Error reading from socket");
459                 return (-1);
460         }
461         else
462         {
463                 /* close the opened socket */
464                 net_close (&sockfd);
465         }
466
467         return (0);
468 }
469
470 #if APCMAIN
471 /*
472  * This is used for testing apcups in a standalone mode.
473  * Usefull for debugging.
474  */
475 int main (int argc, char **argv)
476 {
477         /* we are not really going to use this */
478         struct apc_detail_s apcups_detail;
479
480         openlog ("apcups", LOG_PID | LOG_NDELAY | LOG_LOCAL1, LOG_USER);
481
482         if (global_host == NULL || strcmp (global_host, "0.0.0.0") == 0)
483                 global_host = "localhost";
484
485         if(apc_query_server (global_host, global_port, &apcups_detail) < 0)
486         {
487                 printf("apcups: Failed...\n");
488                 return(-1);
489         }
490
491         return 0;
492 }
493 #else
494 static int apcups_config (char *key, char *value)
495 {
496         if (strcasecmp (key, "host") == 0)
497         {
498                 if (global_host != NULL)
499                 {
500                         free (global_host);
501                         global_host = NULL;
502                 }
503                 if ((global_host = strdup (value)) == NULL)
504                         return (1);
505         }
506         else if (strcasecmp (key, "Port") == 0)
507         {
508                 int port_tmp = atoi (value);
509                 if (port_tmp < 1 || port_tmp > 65535)
510                 {
511                         syslog (LOG_WARNING, "apcups plugin: Invalid port: %i", port_tmp);
512                         return (1);
513                 }
514                 global_port = port_tmp;
515         }
516         else
517         {
518                 return (-1);
519         }
520         return (0);
521 }
522
523 static void apcups_init (void)
524 {
525         return;
526 }
527
528 static void apc_write_voltage (char *host, char *inst, char *val)
529 {
530         char file[512];
531         int  status;
532
533         status = snprintf (file, 512, bvolt_file_template, inst);
534         if ((status < 1) || (status >= 512))
535                 return;
536
537         rrd_update_file (host, file, val, bvolt_ds_def, bvolt_ds_num);
538 }
539
540 static void apc_write_charge (char *host, char *inst, char *val)
541 {
542         rrd_update_file (host, charge_file_template, val, charge_ds_def, charge_ds_num);
543 }
544
545 static void apc_write_percent (char *host, char *inst, char *val)
546 {
547         rrd_update_file (host, load_file_template, val, load_ds_def, load_ds_num);
548 }
549
550 static void apc_write_timeleft (char *host, char *inst, char *val)
551 {
552         rrd_update_file (host, time_file_template, val, time_ds_def, time_ds_num);
553 }
554
555 static void apc_write_temperature (char *host, char *inst, char *val)
556 {
557         rrd_update_file (host, temp_file_template, val, temp_ds_def, temp_ds_num);
558 }
559
560 static void apc_write_frequency (char *host, char *inst, char *val)
561 {
562         char file[512];
563         int  status;
564
565         status = snprintf (file, 512, freq_file_template, inst);
566         if ((status < 1) || (status >= 512))
567                 return;
568
569         rrd_update_file (host, file, val, freq_ds_def, freq_ds_num);
570 }
571
572 static void apc_submit_generic (char *type, char *inst,
573                 double value)
574 {
575         char buf[512];
576         int  status;
577
578         status = snprintf (buf, 512, "%u:%f",
579                         (unsigned int) curtime, value);
580         if ((status < 1) || (status >= 512))
581                 return;
582
583         DBG ("plugin_submit (%s, %s, %s);", type, inst, buf);
584         plugin_submit (type, inst, buf);
585 }
586
587 static void apc_submit (struct apc_detail_s *apcups_detail)
588 {
589         apc_submit_generic ("apcups_voltage",    "input",   apcups_detail->linev);
590         apc_submit_generic ("apcups_voltage",    "output",  apcups_detail->outputv);
591         apc_submit_generic ("apcups_voltage",    "battery", apcups_detail->battv);
592         apc_submit_generic ("apcups_charge",     "-",       apcups_detail->bcharge);
593         apc_submit_generic ("apcups_charge_pct", "-",       apcups_detail->loadpct);
594         apc_submit_generic ("apcups_timeleft",   "-",       apcups_detail->timeleft);
595         apc_submit_generic ("apcups_temp",       "-",       apcups_detail->itemp);
596         apc_submit_generic ("apcups_frequency",  "input",   apcups_detail->linefreq);
597 }
598
599 static void apcups_read (void)
600 {
601         struct apc_detail_s apcups_detail;
602         int status;
603
604         apcups_detail.linev    =   -1.0;
605         apcups_detail.outputv  =   -1.0;
606         apcups_detail.battv    =   -1.0;
607         apcups_detail.loadpct  =   -1.0;
608         apcups_detail.bcharge  =   -1.0;
609         apcups_detail.timeleft =   -1.0;
610         apcups_detail.itemp    = -300.0;
611         apcups_detail.linefreq =   -1.0;
612   
613         status = apc_query_server (global_host == NULL
614                         ? APCUPS_DEFAULT_HOST
615                         : global_host,
616                         global_port, &apcups_detail);
617  
618         /*
619          * if we did not connect then do not bother submitting
620          * zeros. We want rrd files to have NAN.
621          */
622         if (status != 0)
623         {
624                 DBG ("apc_query_server (%s, %i) = %i",
625                                 global_host == NULL
626                                 ? APCUPS_DEFAULT_HOST
627                                 : global_host,
628                                 global_port, status);
629                 return;
630         }
631
632         apc_submit (&apcups_detail);
633 } /* apcups_read */
634
635 void module_register (void)
636 {
637         plugin_register (MODULE_NAME, apcups_init, apcups_read, NULL);
638         plugin_register ("apcups_voltage",    NULL, NULL, apc_write_voltage);
639         plugin_register ("apcups_charge",     NULL, NULL, apc_write_charge);
640         plugin_register ("apcups_charge_pct", NULL, NULL, apc_write_percent);
641         plugin_register ("apcups_timeleft",   NULL, NULL, apc_write_timeleft);
642         plugin_register ("apcups_temp",       NULL, NULL, apc_write_temperature);
643         plugin_register ("apcups_frequency",  NULL, NULL, apc_write_frequency);
644         cf_register (MODULE_NAME, apcups_config, config_keys, config_keys_num);
645 }
646
647 #endif /* if APCMAIN */
648 #undef MODULE_NAME