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