Merge branch 'collectd-5.5'
[collectd.git] / src / olsrd.c
1 /**
2  * collectd - src/olsrd.c
3  * Copyright (C) 2009       Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30
31 #include <sys/types.h>
32 #include <netdb.h>
33 #include <netinet/in.h>
34 #include <netinet/tcp.h>
35
36 #define OLSRD_DEFAULT_NODE "localhost"
37 #define OLSRD_DEFAULT_SERVICE "2006"
38
39 static const char *config_keys[] =
40 {
41   "Host",
42   "Port",
43   "CollectLinks",
44   "CollectRoutes",
45   "CollectTopology"
46 };
47 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
48
49 static char *config_node = NULL;
50 static char *config_service = NULL;
51
52 #define OLSRD_WANT_NOT     0
53 #define OLSRD_WANT_SUMMARY 1
54 #define OLSRD_WANT_DETAIL  2
55 static int config_want_links    = OLSRD_WANT_DETAIL;
56 static int config_want_routes   = OLSRD_WANT_SUMMARY;
57 static int config_want_topology = OLSRD_WANT_SUMMARY;
58
59 static const char *olsrd_get_node (void) /* {{{ */
60 {
61   if (config_node != NULL)
62     return (config_node);
63   return (OLSRD_DEFAULT_NODE);
64 } /* }}} const char *olsrd_get_node */
65
66 static const char *olsrd_get_service (void) /* {{{ */
67 {
68   if (config_service != NULL)
69     return (config_service);
70   return (OLSRD_DEFAULT_SERVICE);
71 } /* }}} const char *olsrd_get_service */
72
73 static void olsrd_set_node (const char *node) /* {{{ */
74 {
75   char *tmp;
76   if (node == NULL)
77     return;
78   tmp = strdup (node);
79   if (tmp == NULL)
80     return;
81   config_node = tmp;
82 } /* }}} void olsrd_set_node */
83
84 static void olsrd_set_service (const char *service) /* {{{ */
85 {
86   char *tmp;
87   if (service == NULL)
88     return;
89   tmp = strdup (service);
90   if (tmp == NULL)
91     return;
92   config_service = tmp;
93 } /* }}} void olsrd_set_service */
94
95 static void olsrd_set_detail (int *varptr, const char *detail, /* {{{ */
96     const char *key)
97 {
98   if (strcasecmp ("No", detail) == 0)
99     *varptr = OLSRD_WANT_NOT;
100   else if (strcasecmp ("Summary", detail) == 0)
101     *varptr = OLSRD_WANT_SUMMARY;
102   else if (strcasecmp ("Detail", detail) == 0)
103     *varptr = OLSRD_WANT_DETAIL;
104   else
105   {
106     ERROR ("olsrd plugin: Invalid argument given to the `%s' configuration "
107         "option: `%s'. Expected: `No', `Summary', or `Detail'.",
108         key, detail);
109   }
110 } /* }}} void olsrd_set_detail */
111
112 /* Strip trailing newline characters. Returns length of string. */
113 static size_t strchomp (char *buffer) /* {{{ */
114 {
115   size_t buffer_len;
116
117   buffer_len = strlen (buffer);
118   while ((buffer_len > 0)
119       && ((buffer[buffer_len - 1] == '\r')
120         || (buffer[buffer_len - 1] == '\n')))
121   {
122     buffer_len--;
123     buffer[buffer_len] = 0;
124   }
125
126   return (buffer_len);
127 } /* }}} size_t strchomp */
128
129 static size_t strtabsplit (char *string, char **fields, size_t size) /* {{{ */
130 {
131   size_t i;
132   char *ptr;
133   char *saveptr;
134
135   i = 0;
136   ptr = string;
137   saveptr = NULL;
138   while ((fields[i] = strtok_r (ptr, " \t\r\n", &saveptr)) != NULL)
139   {
140     ptr = NULL;
141     i++;
142
143     if (i >= size)
144       break;
145   }
146
147   return (i);
148 } /* }}} size_t strtabsplit */
149
150 static FILE *olsrd_connect (void) /* {{{ */
151 {
152   struct addrinfo  ai_hints;
153   struct addrinfo *ai_list, *ai_ptr;
154   int              ai_return;
155
156   FILE *fh;
157
158   memset (&ai_hints, 0, sizeof (ai_hints));
159   ai_hints.ai_flags    = 0;
160 #ifdef AI_ADDRCONFIG
161   ai_hints.ai_flags   |= AI_ADDRCONFIG;
162 #endif
163   ai_hints.ai_family   = PF_UNSPEC;
164   ai_hints.ai_socktype = SOCK_STREAM;
165   ai_hints.ai_protocol = IPPROTO_TCP;
166
167   ai_list = NULL;
168   ai_return = getaddrinfo (olsrd_get_node (), olsrd_get_service (),
169       &ai_hints, &ai_list);
170   if (ai_return != 0)
171   {
172     ERROR ("olsrd plugin: getaddrinfo (%s, %s) failed: %s",
173         olsrd_get_node (), olsrd_get_service (),
174         gai_strerror (ai_return));
175     return (NULL);
176   }
177
178   fh = NULL;
179   for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
180   {
181     int fd;
182     int status;
183     char errbuf[1024];
184
185     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
186     if (fd < 0)
187     {
188       ERROR ("olsrd plugin: socket failed: %s",
189           sstrerror (errno, errbuf, sizeof (errbuf)));
190       continue;
191     }
192
193     status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
194     if (status != 0)
195     {
196       ERROR ("olsrd plugin: connect failed: %s",
197           sstrerror (errno, errbuf, sizeof (errbuf)));
198       close (fd);
199       continue;
200     }
201
202     fh = fdopen (fd, "r+");
203     if (fh == NULL)
204     {
205       ERROR ("olsrd plugin: fdopen failed.");
206       close (fd);
207       continue;
208     }
209
210     break;
211   } /* for (ai_ptr) */
212
213   freeaddrinfo (ai_list);
214
215   return (fh);
216 } /* }}} FILE *olsrd_connect */
217
218 __attribute__ ((nonnull(2)))
219 static void olsrd_submit (const char *plugin_instance, /* {{{ */
220     const char *type, const char *type_instance, gauge_t value)
221 {
222   value_t values[1];
223   value_list_t vl = VALUE_LIST_INIT;
224
225   values[0].gauge = value;
226
227   vl.values = values;
228   vl.values_len = 1;
229
230   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
231   sstrncpy (vl.plugin, "olsrd", sizeof (vl.plugin));
232   if (plugin_instance != NULL)
233     sstrncpy (vl.plugin_instance, plugin_instance,
234         sizeof (vl.plugin_instance));
235   sstrncpy (vl.type, type, sizeof (vl.type));
236   if (type_instance != NULL)
237     sstrncpy (vl.type_instance, type_instance,
238         sizeof (vl.type_instance));
239
240   plugin_dispatch_values (&vl);
241 } /* }}} void olsrd_submit */
242
243 static int olsrd_cb_ignore (int lineno, /* {{{ */
244     size_t fields_num, char **fields)
245 {
246   return (0);
247 } /* }}} int olsrd_cb_ignore */
248
249 static int olsrd_cb_links (int lineno, /* {{{ */
250     size_t fields_num, char **fields)
251 {
252   /* Fields:
253    *  0 = Local IP
254    *  1 = Remote IP
255    *  2 = Hyst.
256    *  3 = LQ
257    *  4 = NLQ
258    *  5 = Cost */
259
260   static uint32_t links_num;
261   static double    lq_sum;
262   static uint32_t  lq_num;
263   static double   nlq_sum;
264   static uint32_t nlq_num;
265
266   double lq;
267   double nlq;
268
269   char *endptr;
270
271   if (config_want_links == OLSRD_WANT_NOT)
272     return (0);
273
274   /* Special handling of the first line. */
275   if (lineno <= 0)
276   {
277     links_num = 0;
278     lq_sum = 0.0;
279     lq_num = 0;
280     nlq_sum = 0.0;
281     nlq_num = 0;
282
283     return (0);
284   }
285
286   /* Special handling of the last line. */
287   if (fields_num == 0)
288   {
289     DEBUG ("olsrd plugin: Number of links: %"PRIu32, links_num);
290     olsrd_submit (/* p.-inst = */ "links", /* type = */ "links",
291         /* t.-inst = */ NULL, (gauge_t) links_num);
292
293     lq = NAN;
294     if (lq_num > 0)
295       lq = lq_sum / ((double) lq_num);
296     DEBUG ("olsrd plugin: Average  LQ: %g", lq);
297     olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
298         "average-lq", lq);
299
300     nlq = NAN;
301     if (nlq_num > 0)
302       nlq = nlq_sum / ((double) nlq_num);
303     DEBUG ("olsrd plugin: Average NLQ: %g", nlq);
304     olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
305         "average-nlq", nlq);
306
307     return (0);
308   }
309
310   if (fields_num != 6)
311     return (-1);
312
313   links_num++;
314
315   errno = 0;
316   endptr = NULL;
317   lq = strtod (fields[3], &endptr);
318   if ((errno != 0) || (endptr == fields[3]))
319   {
320     ERROR ("olsrd plugin: Cannot parse link quality: %s", fields[3]);
321   }
322   else
323   {
324     if (!isnan (lq))
325     {
326       lq_sum += lq;
327       lq_num++;
328     }
329
330     if (config_want_links == OLSRD_WANT_DETAIL)
331     {
332       char type_instance[DATA_MAX_NAME_LEN];
333
334       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-lq",
335           fields[0], fields[1]);
336
337       DEBUG ("olsrd plugin: links: type_instance = %s;  lq = %g;",
338           type_instance, lq);
339       olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
340           type_instance, lq);
341     }
342   }
343
344   errno = 0;
345   endptr = NULL;
346   nlq = strtod (fields[4], &endptr);
347   if ((errno != 0) || (endptr == fields[4]))
348   {
349     ERROR ("olsrd plugin: Cannot parse neighbor link quality: %s", fields[4]);
350   }
351   else
352   {
353     if (!isnan (nlq))
354     {
355       nlq_sum += nlq;
356       nlq_num++;
357     }
358
359     if (config_want_links == OLSRD_WANT_DETAIL)
360     {
361       char type_instance[DATA_MAX_NAME_LEN];
362
363       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-rx",
364           fields[0], fields[1]);
365
366       DEBUG ("olsrd plugin: links: type_instance = %s; nlq = %g;",
367           type_instance, lq);
368       olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
369           type_instance, nlq);
370     }
371   }
372
373   return (0);
374 } /* }}} int olsrd_cb_links */
375
376 static int olsrd_cb_routes (int lineno, /* {{{ */
377     size_t fields_num, char **fields)
378 {
379   /* Fields:
380    *  0 = Destination
381    *  1 = Gateway IP
382    *  2 = Metric
383    *  3 = ETX
384    *  4 = Interface */
385
386   static uint32_t routes_num;
387   static uint32_t metric_sum;
388   static uint32_t metric_num;
389   static double   etx_sum;
390   static uint32_t etx_num;
391
392   uint32_t metric;
393   double etx;
394   char *endptr;
395
396   if (config_want_routes == OLSRD_WANT_NOT)
397     return (0);
398
399   /* Special handling of the first line */
400   if (lineno <= 0)
401   {
402     routes_num = 0;
403     metric_num = 0;
404     metric_sum = 0;
405     etx_sum = 0.0;
406     etx_num = 0;
407
408     return (0);
409   }
410
411   /* Special handling after the last line */
412   if (fields_num == 0)
413   {
414     double metric_avg;
415
416     DEBUG ("olsrd plugin: Number of routes: %"PRIu32, routes_num);
417     olsrd_submit (/* p.-inst = */ "routes", /* type = */ "routes",
418         /* t.-inst = */ NULL, (gauge_t) routes_num);
419
420     metric_avg = NAN;
421     if (metric_num > 0)
422       metric_avg = ((double) metric_sum) / ((double) metric_num);
423     DEBUG ("olsrd plugin: Average metric: %g", metric_avg);
424     olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_metric",
425         "average", metric_avg);
426
427     etx = NAN;
428     if (etx_num > 0)
429       etx = etx_sum / ((double) etx_sum);
430     DEBUG ("olsrd plugin: Average ETX: %g", etx);
431     olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_etx",
432         "average", etx);
433
434     return (0);
435   }
436
437   if (fields_num != 5)
438     return (-1);
439
440   routes_num++;
441
442   errno = 0;
443   endptr = NULL;
444   metric = (uint32_t) strtoul (fields[2], &endptr, 0);
445   if ((errno != 0) || (endptr == fields[2]))
446   {
447     ERROR ("olsrd plugin: Unable to parse metric: %s", fields[2]);
448   }
449   else
450   {
451     metric_num++;
452     metric_sum += metric;
453
454     if (config_want_routes == OLSRD_WANT_DETAIL)
455     {
456       DEBUG ("olsrd plugin: destination = %s; metric = %"PRIu32";",
457           fields[0], metric);
458       olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_metric",
459           /* t.-inst = */ fields[0], (gauge_t) metric);
460     }
461   }
462
463   errno = 0;
464   endptr = NULL;
465   etx = strtod (fields[3], &endptr);
466   if ((errno != 0) || (endptr == fields[3]))
467   {
468     ERROR ("olsrd plugin: Unable to parse ETX: %s", fields[3]);
469   }
470   else
471   {
472     if (!isnan (etx))
473     {
474       etx_sum += etx;
475       etx_num++;
476     }
477
478     if (config_want_routes == OLSRD_WANT_DETAIL)
479     {
480       DEBUG ("olsrd plugin: destination = %s; etx = %g;",
481           fields[0], etx);
482       olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_etx",
483           /* t.-inst = */ fields[0], etx);
484     }
485   }
486
487   return (0);
488 } /* }}} int olsrd_cb_routes */
489
490 static int olsrd_cb_topology (int lineno, /* {{{ */
491     size_t fields_num, char **fields)
492 {
493   /* Fields:
494    *  0 = Dest. IP
495    *  1 = Last hop IP
496    *  2 = LQ
497    *  3 = NLQ
498    *  4 = Cost */
499
500   static double   lq_sum;
501   static uint32_t lq_num;
502
503   static uint32_t links_num;
504
505   double lq;
506   char *endptr;
507
508   if (config_want_topology == OLSRD_WANT_NOT)
509     return (0);
510
511   /* Special handling of the first line */
512   if (lineno <= 0)
513   {
514     lq_sum = 0.0;
515     lq_num = 0;
516     links_num = 0;
517
518     return (0);
519   }
520
521   /* Special handling after the last line */
522   if (fields_num == 0)
523   {
524     DEBUG ("olsrd plugin: topology: Number of links: %"PRIu32, links_num);
525     olsrd_submit (/* p.-inst = */ "topology", /* type = */ "links",
526         /* t.-inst = */ NULL, (gauge_t) links_num);
527
528     lq = NAN;
529     if (lq_num > 0)
530       lq = lq_sum / ((double) lq_sum);
531     DEBUG ("olsrd plugin: topology: Average link quality: %g", lq);
532     olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
533         /* t.-inst = */ "average", lq);
534
535     return (0);
536   }
537
538   if (fields_num != 5)
539     return (-1);
540
541   links_num++;
542
543   errno = 0;
544   endptr = NULL;
545   lq = strtod (fields[2], &endptr);
546   if ((errno != 0) || (endptr == fields[2]))
547   {
548     ERROR ("olsrd plugin: Unable to parse LQ: %s", fields[2]);
549   }
550   else
551   {
552     if (!isnan (lq))
553     {
554       lq_sum += lq;
555       lq_num++;
556     }
557
558     if (config_want_topology == OLSRD_WANT_DETAIL)
559     {
560       char type_instance[DATA_MAX_NAME_LEN];
561
562       memset (type_instance, 0, sizeof (type_instance));
563       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-lq",
564           fields[0], fields[1]);
565       DEBUG ("olsrd plugin: type_instance = %s; lq = %g;", type_instance, lq);
566       olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
567           type_instance, lq);
568     }
569   }
570
571   if (config_want_topology == OLSRD_WANT_DETAIL)
572   {
573     double nlq;
574
575     errno = 0;
576     endptr = NULL;
577     nlq = strtod (fields[3], &endptr);
578     if ((errno != 0) || (endptr == fields[3]))
579     {
580       ERROR ("olsrd plugin: Unable to parse NLQ: %s", fields[3]);
581     }
582     else
583     {
584       char type_instance[DATA_MAX_NAME_LEN];
585
586       memset (type_instance, 0, sizeof (type_instance));
587       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-nlq",
588           fields[0], fields[1]);
589       DEBUG ("olsrd plugin: type_instance = %s; nlq = %g;", type_instance, nlq);
590       olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
591           type_instance, nlq);
592     }
593   }
594
595   return (0);
596 } /* }}} int olsrd_cb_topology */
597
598 static int olsrd_read_table (FILE *fh, /* {{{ */
599     int (*callback) (int lineno, size_t fields_num, char **fields))
600 {
601   char buffer[1024];
602   size_t buffer_len;
603
604   char *fields[32];
605   size_t fields_num;
606
607   int lineno;
608
609   lineno = 0;
610   while (fgets (buffer, sizeof (buffer), fh) != NULL)
611   {
612     /* An empty line ends the table. */
613     buffer_len = strchomp (buffer);
614     if (buffer_len <= 0)
615     {
616       (*callback) (lineno, /* fields_num = */ 0, /* fields = */ NULL);
617       break;
618     }
619
620     fields_num = strtabsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
621
622     (*callback) (lineno, fields_num, fields);
623     lineno++;
624   } /* while (fgets) */
625   
626   return (0);
627 } /* }}} int olsrd_read_table */
628
629 static int olsrd_config (const char *key, const char *value) /* {{{ */
630 {
631   if (strcasecmp ("Host", key) == 0)
632     olsrd_set_node (value);
633   else if (strcasecmp ("Port", key) == 0)
634     olsrd_set_service (value);
635   else if (strcasecmp ("CollectLinks", key) == 0)
636     olsrd_set_detail (&config_want_links, value, key);
637   else if (strcasecmp ("CollectRoutes", key) == 0)
638     olsrd_set_detail (&config_want_routes, value, key);
639   else if (strcasecmp ("CollectTopology", key) == 0)
640     olsrd_set_detail (&config_want_topology, value, key);
641   else
642   {
643     ERROR ("olsrd plugin: Unknown configuration option given: %s", key);
644     return (-1);
645   }
646
647   return (0);
648 } /* }}} int olsrd_config */
649
650 static int olsrd_read (void) /* {{{ */
651 {
652   FILE *fh;
653   char buffer[1024];
654   size_t buffer_len;
655
656   fh = olsrd_connect ();
657   if (fh == NULL)
658     return (-1);
659
660   fputs ("\r\n", fh);
661   fflush (fh);
662
663   while (fgets (buffer, sizeof (buffer), fh) != NULL)
664   {
665     buffer_len = strchomp (buffer);
666     if (buffer_len <= 0)
667       continue;
668     
669     if (strcmp ("Table: Links", buffer) == 0)
670       olsrd_read_table (fh, olsrd_cb_links);
671     else if (strcmp ("Table: Neighbors", buffer) == 0)
672       olsrd_read_table (fh, olsrd_cb_ignore);
673     else if (strcmp ("Table: Topology", buffer) == 0)
674       olsrd_read_table (fh, olsrd_cb_topology);
675     else if (strcmp ("Table: HNA", buffer) == 0)
676       olsrd_read_table (fh, olsrd_cb_ignore);
677     else if (strcmp ("Table: MID", buffer) == 0)
678       olsrd_read_table (fh, olsrd_cb_ignore);
679     else if (strcmp ("Table: Routes", buffer) == 0)
680       olsrd_read_table (fh, olsrd_cb_routes);
681     else if ((strcmp ("HTTP/1.0 200 OK", buffer) == 0)
682         || (strcmp ("Content-type: text/plain", buffer) == 0))
683     {
684       /* ignore */
685     }
686     else
687     {
688       DEBUG ("olsrd plugin: Unable to handle line: %s", buffer);
689     }
690   } /* while (fgets) */
691
692   fclose (fh);
693   
694   return (0);
695 } /* }}} int olsrd_read */
696
697 static int olsrd_shutdown (void) /* {{{ */
698 {
699   sfree (config_node);
700   sfree (config_service);
701
702   return (0);
703 } /* }}} int olsrd_shutdown */
704
705 void module_register (void)
706 {
707   plugin_register_config ("olsrd", olsrd_config,
708       config_keys, config_keys_num);
709   plugin_register_read ("olsrd", olsrd_read);
710   plugin_register_shutdown ("olsrd", olsrd_shutdown);
711 } /* void module_register */
712
713 /* vim: set sw=2 sts=2 et fdm=marker : */