Merge pull request #2046 from rubenk/cleanup-dpdk-detection
[collectd.git] / src / ping.c
1 /**
2  * collectd - src/ping.c
3  * Copyright (C) 2005-2012  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
29 #include "common.h"
30 #include "plugin.h"
31 #include "utils_complain.h"
32
33 #include <netinet/in.h>
34 #if HAVE_NETDB_H
35 # include <netdb.h> /* NI_MAXHOST */
36 #endif
37
38 #ifdef HAVE_SYS_CAPABILITY_H
39 # include <sys/capability.h>
40 #endif
41
42 #include <oping.h>
43
44 #ifndef NI_MAXHOST
45 # define NI_MAXHOST 1025
46 #endif
47
48 #if defined(OPING_VERSION) && (OPING_VERSION >= 1003000)
49 # define HAVE_OPING_1_3
50 #endif
51
52 /*
53  * Private data types
54  */
55 struct hostlist_s
56 {
57   char *host;
58
59   uint32_t pkg_sent;
60   uint32_t pkg_recv;
61   uint32_t pkg_missed;
62
63   double latency_total;
64   double latency_squared;
65
66   struct hostlist_s *next;
67 };
68 typedef struct hostlist_s hostlist_t;
69
70 /*
71  * Private variables
72  */
73 static hostlist_t *hostlist_head = NULL;
74
75 static char  *ping_source = NULL;
76 #ifdef HAVE_OPING_1_3
77 static char  *ping_device = NULL;
78 #endif
79 static char  *ping_data = NULL;
80 static int    ping_ttl = PING_DEF_TTL;
81 static double ping_interval = 1.0;
82 static double ping_timeout = 0.9;
83 static int    ping_max_missed = -1;
84
85 static int             ping_thread_loop = 0;
86 static int             ping_thread_error = 0;
87 static pthread_t       ping_thread_id;
88 static pthread_mutex_t ping_lock = PTHREAD_MUTEX_INITIALIZER;
89 static pthread_cond_t  ping_cond = PTHREAD_COND_INITIALIZER;
90
91 static const char *config_keys[] =
92 {
93   "Host",
94   "SourceAddress",
95 #ifdef HAVE_OPING_1_3
96   "Device",
97 #endif
98   "Size",
99   "TTL",
100   "Interval",
101   "Timeout",
102   "MaxMissed"
103 };
104 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
105
106 /*
107  * Private functions
108  */
109 /* Assure that `ts->tv_nsec' is in the range 0 .. 999999999 */
110 static void time_normalize (struct timespec *ts) /* {{{ */
111 {
112   while (ts->tv_nsec < 0)
113   {
114     if (ts->tv_sec == 0)
115     {
116       ts->tv_nsec = 0;
117       return;
118     }
119
120     ts->tv_sec  -= 1;
121     ts->tv_nsec += 1000000000;
122   }
123
124   while (ts->tv_nsec >= 1000000000)
125   {
126     ts->tv_sec  += 1;
127     ts->tv_nsec -= 1000000000;
128   }
129 } /* }}} void time_normalize */
130
131 /* Add `ts_int' to `tv_begin' and store the result in `ts_dest'. If the result
132  * is larger than `tv_end', copy `tv_end' to `ts_dest' instead. */
133 static void time_calc (struct timespec *ts_dest, /* {{{ */
134     const struct timespec *ts_int,
135     const struct timeval  *tv_begin,
136     const struct timeval  *tv_end)
137 {
138   ts_dest->tv_sec = tv_begin->tv_sec + ts_int->tv_sec;
139   ts_dest->tv_nsec = (tv_begin->tv_usec * 1000) + ts_int->tv_nsec;
140   time_normalize (ts_dest);
141
142   /* Assure that `(begin + interval) > end'.
143    * This may seem overly complicated, but `tv_sec' is of type `time_t'
144    * which may be `unsigned. *sigh* */
145   if ((tv_end->tv_sec > ts_dest->tv_sec)
146       || ((tv_end->tv_sec == ts_dest->tv_sec)
147         && ((tv_end->tv_usec * 1000) > ts_dest->tv_nsec)))
148   {
149     ts_dest->tv_sec = tv_end->tv_sec;
150     ts_dest->tv_nsec = 1000 * tv_end->tv_usec;
151   }
152
153   time_normalize (ts_dest);
154 } /* }}} void time_calc */
155
156 static int ping_dispatch_all (pingobj_t *pingobj) /* {{{ */
157 {
158   hostlist_t *hl;
159   int status;
160
161   for (pingobj_iter_t *iter = ping_iterator_get (pingobj);
162       iter != NULL;
163       iter = ping_iterator_next (iter))
164   { /* {{{ */
165     char userhost[NI_MAXHOST];
166     double latency;
167     size_t param_size;
168
169     param_size = sizeof (userhost);
170     status = ping_iterator_get_info (iter,
171 #ifdef PING_INFO_USERNAME
172         PING_INFO_USERNAME,
173 #else
174         PING_INFO_HOSTNAME,
175 #endif
176         userhost, &param_size);
177     if (status != 0)
178     {
179       WARNING ("ping plugin: ping_iterator_get_info failed: %s",
180           ping_get_error (pingobj));
181       continue;
182     }
183
184     for (hl = hostlist_head; hl != NULL; hl = hl->next)
185       if (strcmp (userhost, hl->host) == 0)
186         break;
187
188     if (hl == NULL)
189     {
190       WARNING ("ping plugin: Cannot find host %s.", userhost);
191       continue;
192     }
193
194     param_size = sizeof (latency);
195     status = ping_iterator_get_info (iter, PING_INFO_LATENCY,
196         (void *) &latency, &param_size);
197     if (status != 0)
198     {
199       WARNING ("ping plugin: ping_iterator_get_info failed: %s",
200           ping_get_error (pingobj));
201       continue;
202     }
203
204     hl->pkg_sent++;
205     if (latency >= 0.0)
206     {
207       hl->pkg_recv++;
208       hl->latency_total += latency;
209       hl->latency_squared += (latency * latency);
210
211       /* reset missed packages counter */
212       hl->pkg_missed = 0;
213     } else
214       hl->pkg_missed++;
215
216     /* if the host did not answer our last N packages, trigger a resolv. */
217     if ((ping_max_missed >= 0)
218         && (hl->pkg_missed >= ((uint32_t) ping_max_missed)))
219     { /* {{{ */
220       /* we reset the missed package counter here, since we only want to
221        * trigger a resolv every N packages and not every package _AFTER_ N
222        * missed packages */
223       hl->pkg_missed = 0;
224
225       WARNING ("ping plugin: host %s has not answered %d PING requests,"
226           " triggering resolve", hl->host, ping_max_missed);
227
228       /* we trigger the resolv simply be removeing and adding the host to our
229        * ping object */
230       status = ping_host_remove (pingobj, hl->host);
231       if (status != 0)
232       {
233         WARNING ("ping plugin: ping_host_remove (%s) failed.", hl->host);
234       }
235       else
236       {
237         status = ping_host_add (pingobj, hl->host);
238         if (status != 0)
239           ERROR ("ping plugin: ping_host_add (%s) failed.", hl->host);
240       }
241     } /* }}} ping_max_missed */
242   } /* }}} for (iter) */
243
244   return (0);
245 } /* }}} int ping_dispatch_all */
246
247 static void *ping_thread (void *arg) /* {{{ */
248 {
249   pingobj_t *pingobj = NULL;
250
251   struct timeval  tv_begin;
252   struct timeval  tv_end;
253   struct timespec ts_wait;
254   struct timespec ts_int;
255
256   int count;
257
258   c_complain_t complaint = C_COMPLAIN_INIT_STATIC;
259
260   pthread_mutex_lock (&ping_lock);
261
262   pingobj = ping_construct ();
263   if (pingobj == NULL)
264   {
265     ERROR ("ping plugin: ping_construct failed.");
266     ping_thread_error = 1;
267     pthread_mutex_unlock (&ping_lock);
268     return ((void *) -1);
269   }
270
271   if (ping_source != NULL)
272     if (ping_setopt (pingobj, PING_OPT_SOURCE, (void *) ping_source) != 0)
273       ERROR ("ping plugin: Failed to set source address: %s",
274           ping_get_error (pingobj));
275
276 #ifdef HAVE_OPING_1_3
277   if (ping_device != NULL)
278     if (ping_setopt (pingobj, PING_OPT_DEVICE, (void *) ping_device) != 0)
279       ERROR ("ping plugin: Failed to set device: %s",
280           ping_get_error (pingobj));
281 #endif
282
283   ping_setopt (pingobj, PING_OPT_TIMEOUT, (void *) &ping_timeout);
284   ping_setopt (pingobj, PING_OPT_TTL, (void *) &ping_ttl);
285
286   if (ping_data != NULL)
287     ping_setopt (pingobj, PING_OPT_DATA, (void *) ping_data);
288
289   /* Add all the hosts to the ping object. */
290   count = 0;
291   for (hostlist_t *hl = hostlist_head; hl != NULL; hl = hl->next)
292   {
293     int tmp_status;
294     tmp_status = ping_host_add (pingobj, hl->host);
295     if (tmp_status != 0)
296       WARNING ("ping plugin: ping_host_add (%s) failed: %s",
297           hl->host, ping_get_error (pingobj));
298     else
299       count++;
300   }
301
302   if (count == 0)
303   {
304     ERROR ("ping plugin: No host could be added to ping object. Giving up.");
305     ping_thread_error = 1;
306     pthread_mutex_unlock (&ping_lock);
307     return ((void *) -1);
308   }
309
310   /* Set up `ts_int' */
311   {
312     double temp_sec;
313     double temp_nsec;
314
315     temp_nsec = modf (ping_interval, &temp_sec);
316     ts_int.tv_sec  = (time_t) temp_sec;
317     ts_int.tv_nsec = (long) (temp_nsec * 1000000000L);
318   }
319
320   while (ping_thread_loop > 0)
321   {
322     int status;
323     _Bool send_successful = 0;
324
325     if (gettimeofday (&tv_begin, NULL) < 0)
326     {
327       char errbuf[1024];
328       ERROR ("ping plugin: gettimeofday failed: %s",
329           sstrerror (errno, errbuf, sizeof (errbuf)));
330       ping_thread_error = 1;
331       break;
332     }
333
334     pthread_mutex_unlock (&ping_lock);
335
336     status = ping_send (pingobj);
337     if (status < 0)
338     {
339       c_complain (LOG_ERR, &complaint, "ping plugin: ping_send failed: %s",
340           ping_get_error (pingobj));
341     }
342     else
343     {
344       c_release (LOG_NOTICE, &complaint, "ping plugin: ping_send succeeded.");
345       send_successful = 1;
346     }
347
348     pthread_mutex_lock (&ping_lock);
349
350     if (ping_thread_loop <= 0)
351       break;
352
353     if (send_successful)
354       (void) ping_dispatch_all (pingobj);
355
356     if (gettimeofday (&tv_end, NULL) < 0)
357     {
358       char errbuf[1024];
359       ERROR ("ping plugin: gettimeofday failed: %s",
360           sstrerror (errno, errbuf, sizeof (errbuf)));
361       ping_thread_error = 1;
362       break;
363     }
364
365     /* Calculate the absolute time until which to wait and store it in
366      * `ts_wait'. */
367     time_calc (&ts_wait, &ts_int, &tv_begin, &tv_end);
368
369     pthread_cond_timedwait (&ping_cond, &ping_lock, &ts_wait);
370     if (ping_thread_loop <= 0)
371       break;
372   } /* while (ping_thread_loop > 0) */
373
374   pthread_mutex_unlock (&ping_lock);
375   ping_destroy (pingobj);
376
377   return ((void *) 0);
378 } /* }}} void *ping_thread */
379
380 static int start_thread (void) /* {{{ */
381 {
382   int status;
383
384   pthread_mutex_lock (&ping_lock);
385
386   if (ping_thread_loop != 0)
387   {
388     pthread_mutex_unlock (&ping_lock);
389     return (0);
390   }
391
392   ping_thread_loop = 1;
393   ping_thread_error = 0;
394   status = plugin_thread_create (&ping_thread_id, /* attr = */ NULL,
395       ping_thread, /* arg = */ (void *) 0, "ping");
396   if (status != 0)
397   {
398     ping_thread_loop = 0;
399     ERROR ("ping plugin: Starting thread failed.");
400     pthread_mutex_unlock (&ping_lock);
401     return (-1);
402   }
403
404   pthread_mutex_unlock (&ping_lock);
405   return (0);
406 } /* }}} int start_thread */
407
408 static int stop_thread (void) /* {{{ */
409 {
410   int status;
411
412   pthread_mutex_lock (&ping_lock);
413
414   if (ping_thread_loop == 0)
415   {
416     pthread_mutex_unlock (&ping_lock);
417     return (-1);
418   }
419
420   ping_thread_loop = 0;
421   pthread_cond_broadcast (&ping_cond);
422   pthread_mutex_unlock (&ping_lock);
423
424   status = pthread_join (ping_thread_id, /* return = */ NULL);
425   if (status != 0)
426   {
427     ERROR ("ping plugin: Stopping thread failed.");
428     status = -1;
429   }
430
431   pthread_mutex_lock (&ping_lock);
432   memset (&ping_thread_id, 0, sizeof (ping_thread_id));
433   ping_thread_error = 0;
434   pthread_mutex_unlock (&ping_lock);
435
436   return (status);
437 } /* }}} int stop_thread */
438
439 static int ping_init (void) /* {{{ */
440 {
441   if (hostlist_head == NULL)
442   {
443     NOTICE ("ping plugin: No hosts have been configured.");
444     return (-1);
445   }
446
447   if (ping_timeout > ping_interval)
448   {
449     ping_timeout = 0.9 * ping_interval;
450     WARNING ("ping plugin: Timeout is greater than interval. "
451         "Will use a timeout of %gs.", ping_timeout);
452   }
453
454 #if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_NET_RAW)
455   if (check_capability (CAP_NET_RAW) != 0)
456   {
457     if (getuid () == 0)
458       WARNING ("ping plugin: Running collectd as root, but the CAP_NET_RAW "
459           "capability is missing. The plugin's read function will probably "
460           "fail. Is your init system dropping capabilities?");
461     else
462       WARNING ("ping plugin: collectd doesn't have the CAP_NET_RAW capability. "
463           "If you don't want to run collectd as root, try running \"setcap "
464           "cap_net_raw=ep\" on the collectd binary.");
465   }
466 #endif
467
468   return (start_thread ());
469 } /* }}} int ping_init */
470
471 static int config_set_string (const char *name, /* {{{ */
472     char **var, const char *value)
473 {
474   char *tmp;
475
476   tmp = strdup (value);
477   if (tmp == NULL)
478   {
479     char errbuf[1024];
480     ERROR ("ping plugin: Setting `%s' to `%s' failed: strdup failed: %s",
481         name, value, sstrerror (errno, errbuf, sizeof (errbuf)));
482     return (1);
483   }
484
485   if (*var != NULL)
486     free (*var);
487   *var = tmp;
488   return (0);
489 } /* }}} int config_set_string */
490
491 static int ping_config (const char *key, const char *value) /* {{{ */
492 {
493   if (strcasecmp (key, "Host") == 0)
494   {
495     hostlist_t *hl;
496     char *host;
497
498     hl = malloc (sizeof (*hl));
499     if (hl == NULL)
500     {
501       char errbuf[1024];
502       ERROR ("ping plugin: malloc failed: %s",
503           sstrerror (errno, errbuf, sizeof (errbuf)));
504       return (1);
505     }
506
507     host = strdup (value);
508     if (host == NULL)
509     {
510       char errbuf[1024];
511       sfree (hl);
512       ERROR ("ping plugin: strdup failed: %s",
513           sstrerror (errno, errbuf, sizeof (errbuf)));
514       return (1);
515     }
516
517     hl->host = host;
518     hl->pkg_sent = 0;
519     hl->pkg_recv = 0;
520     hl->pkg_missed = 0;
521     hl->latency_total = 0.0;
522     hl->latency_squared = 0.0;
523     hl->next = hostlist_head;
524     hostlist_head = hl;
525   }
526   else if (strcasecmp (key, "SourceAddress") == 0)
527   {
528     int status = config_set_string (key, &ping_source, value);
529     if (status != 0)
530       return (status);
531   }
532 #ifdef HAVE_OPING_1_3
533   else if (strcasecmp (key, "Device") == 0)
534   {
535     int status = config_set_string (key, &ping_device, value);
536     if (status != 0)
537       return (status);
538   }
539 #endif
540   else if (strcasecmp (key, "TTL") == 0)
541   {
542     int ttl = atoi (value);
543     if ((ttl > 0) && (ttl <= 255))
544       ping_ttl = ttl;
545     else
546       WARNING ("ping plugin: Ignoring invalid TTL %i.", ttl);
547   }
548   else if (strcasecmp (key, "Interval") == 0)
549   {
550     double tmp;
551
552     tmp = atof (value);
553     if (tmp > 0.0)
554       ping_interval = tmp;
555     else
556       WARNING ("ping plugin: Ignoring invalid interval %g (%s)",
557           tmp, value);
558   }
559   else if (strcasecmp (key, "Size") == 0) {
560     size_t size = (size_t) atoi (value);
561
562     /* Max IP packet size - (IPv6 + ICMP) = 65535 - (40 + 8) = 65487 */
563     if (size <= 65487)
564     {
565       sfree (ping_data);
566       ping_data = malloc (size + 1);
567       if (ping_data == NULL)
568       {
569         ERROR ("ping plugin: malloc failed.");
570         return (1);
571       }
572
573       /* Note: By default oping is using constant string
574        * "liboping -- ICMP ping library <http://octo.it/liboping/>"
575        * which is exactly 56 bytes.
576        *
577        * Optimally we would follow the ping(1) behaviour, but we
578        * cannot use byte 00 or start data payload at exactly same
579        * location, due to oping library limitations. */
580       for (size_t i = 0; i < size; i++) /* {{{ */
581       {
582         /* This restricts data pattern to be only composed of easily
583          * printable characters, and not NUL character. */
584         ping_data[i] = ('0' + i % 64);
585       }  /* }}} for (i = 0; i < size; i++) */
586       ping_data[size] = 0;
587     } else
588       WARNING ("ping plugin: Ignoring invalid Size %zu.", size);
589   }
590   else if (strcasecmp (key, "Timeout") == 0)
591   {
592     double tmp;
593
594     tmp = atof (value);
595     if (tmp > 0.0)
596       ping_timeout = tmp;
597     else
598       WARNING ("ping plugin: Ignoring invalid timeout %g (%s)",
599           tmp, value);
600   }
601   else if (strcasecmp (key, "MaxMissed") == 0)
602   {
603     ping_max_missed = atoi (value);
604     if (ping_max_missed < 0)
605       INFO ("ping plugin: MaxMissed < 0, disabled re-resolving of hosts");
606   }
607   else
608   {
609     return (-1);
610   }
611
612   return (0);
613 } /* }}} int ping_config */
614
615 static void submit (const char *host, const char *type, /* {{{ */
616     gauge_t value)
617 {
618   value_list_t vl = VALUE_LIST_INIT;
619
620   vl.values = &(value_t) { .gauge = value };
621   vl.values_len = 1;
622   sstrncpy (vl.plugin, "ping", sizeof (vl.plugin));
623   sstrncpy (vl.type_instance, host, sizeof (vl.type_instance));
624   sstrncpy (vl.type, type, sizeof (vl.type));
625
626   plugin_dispatch_values (&vl);
627 } /* }}} void ping_submit */
628
629 static int ping_read (void) /* {{{ */
630 {
631   if (ping_thread_error != 0)
632   {
633     ERROR ("ping plugin: The ping thread had a problem. Restarting it.");
634
635     stop_thread ();
636
637     for (hostlist_t *hl = hostlist_head; hl != NULL; hl = hl->next)
638     {
639       hl->pkg_sent = 0;
640       hl->pkg_recv = 0;
641       hl->latency_total = 0.0;
642       hl->latency_squared = 0.0;
643     }
644
645     start_thread ();
646
647     return (-1);
648   } /* if (ping_thread_error != 0) */
649
650   for (hostlist_t *hl = hostlist_head; hl != NULL; hl = hl->next) /* {{{ */
651   {
652     uint32_t pkg_sent;
653     uint32_t pkg_recv;
654     double latency_total;
655     double latency_squared;
656
657     double latency_average;
658     double latency_stddev;
659
660     double droprate;
661
662     /* Locking here works, because the structure of the linked list is only
663      * changed during configure and shutdown. */
664     pthread_mutex_lock (&ping_lock);
665
666     pkg_sent = hl->pkg_sent;
667     pkg_recv = hl->pkg_recv;
668     latency_total = hl->latency_total;
669     latency_squared = hl->latency_squared;
670
671     hl->pkg_sent = 0;
672     hl->pkg_recv = 0;
673     hl->latency_total = 0.0;
674     hl->latency_squared = 0.0;
675
676     pthread_mutex_unlock (&ping_lock);
677
678     /* This e. g. happens when starting up. */
679     if (pkg_sent == 0)
680     {
681       DEBUG ("ping plugin: No packages for host %s have been sent.",
682           hl->host);
683       continue;
684     }
685
686     /* Calculate average. Beware of division by zero. */
687     if (pkg_recv == 0)
688       latency_average = NAN;
689     else
690       latency_average = latency_total / ((double) pkg_recv);
691
692     /* Calculate standard deviation. Beware even more of division by zero. */
693     if (pkg_recv == 0)
694       latency_stddev = NAN;
695     else if (pkg_recv == 1)
696       latency_stddev = 0.0;
697     else
698       latency_stddev = sqrt (((((double) pkg_recv) * latency_squared)
699           - (latency_total * latency_total))
700           / ((double) (pkg_recv * (pkg_recv - 1))));
701
702     /* Calculate drop rate. */
703     droprate = ((double) (pkg_sent - pkg_recv)) / ((double) pkg_sent);
704
705     submit (hl->host, "ping", latency_average);
706     submit (hl->host, "ping_stddev", latency_stddev);
707     submit (hl->host, "ping_droprate", droprate);
708   } /* }}} for (hl = hostlist_head; hl != NULL; hl = hl->next) */
709
710   return (0);
711 } /* }}} int ping_read */
712
713 static int ping_shutdown (void) /* {{{ */
714 {
715   hostlist_t *hl;
716
717   INFO ("ping plugin: Shutting down thread.");
718   if (stop_thread () < 0)
719     return (-1);
720
721   hl = hostlist_head;
722   while (hl != NULL)
723   {
724     hostlist_t *hl_next;
725
726     hl_next = hl->next;
727
728     sfree (hl->host);
729     sfree (hl);
730
731     hl = hl_next;
732   }
733
734   if (ping_data != NULL) {
735     free (ping_data);
736     ping_data = NULL;
737   }
738
739   return (0);
740 } /* }}} int ping_shutdown */
741
742 void module_register (void)
743 {
744   plugin_register_config ("ping", ping_config,
745       config_keys, config_keys_num);
746   plugin_register_init ("ping", ping_init);
747   plugin_register_read ("ping", ping_read);
748   plugin_register_shutdown ("ping", ping_shutdown);
749 } /* void module_register */
750
751 /* vim: set sw=2 sts=2 et fdm=marker : */