Code

collectd-nagios, rrdtool plugin, unixsock plugin: Use `isnan' rather than `==' or...
[collectd.git] / src / rrdtool.c
1 /**
2  * collectd - src/rrdtool.c
3  * Copyright (C) 2006  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  **/
22 #include "collectd.h"
23 #include "plugin.h"
24 #include "common.h"
25 #include "utils_avltree.h"
26 #include "utils_debug.h"
28 /*
29  * Private types
30  */
31 struct rrd_cache_s
32 {
33         int    values_num;
34         char **values;
35         time_t first_value;
36 };
37 typedef struct rrd_cache_s rrd_cache_t;
39 /*
40  * Private variables
41  */
42 static int rra_timespans[] =
43 {
44         3600,
45         86400,
46         604800,
47         2678400,
48         31622400
49 };
50 static int rra_timespans_num = STATIC_ARRAY_SIZE (rra_timespans);
52 static char *rra_types[] =
53 {
54         "AVERAGE",
55         "MIN",
56         "MAX"
57 };
58 static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
60 static const char *config_keys[] =
61 {
62         "CacheTimeout",
63         "CacheFlush",
64         "DataDir",
65         "StepSize",
66         "HeartBeat",
67         "RRARows",
68         "XFF"
69 };
70 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
72 static char   *datadir   = NULL;
73 static int     stepsize  = 0;
74 static int     heartbeat = 0;
75 static int     rrarows   = 1200;
76 static double  xff       = 0.1;
78 static int         cache_timeout = 0;
79 static int         cache_flush_timeout = 0;
80 static time_t      cache_flush_last;
81 static avl_tree_t *cache = NULL;
83 /* * * * * * * * * *
84  * WARNING:  Magic *
85  * * * * * * * * * */
86 static int rra_get (char ***ret)
87 {
88         static char **rra_def = NULL;
89         static int rra_num = 0;
91         int rra_max = rra_timespans_num * rra_types_num;
93         int span;
95         int cdp_num;
96         int cdp_len;
97         int i, j;
99         char buffer[64];
101         if ((rra_num != 0) && (rra_def != NULL))
102         {
103                 *ret = rra_def;
104                 return (rra_num);
105         }
107         if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
108                 return (-1);
109         memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
111         if ((stepsize <= 0) || (rrarows <= 0))
112         {
113                 *ret = NULL;
114                 return (-1);
115         }
117         cdp_len = 0;
118         for (i = 0; i < rra_timespans_num; i++)
119         {
120                 span = rra_timespans[i];
122                 if ((span / stepsize) < rrarows)
123                         continue;
125                 if (cdp_len == 0)
126                         cdp_len = 1;
127                 else
128                         cdp_len = (int) floor (((double) span)
129                                         / ((double) (rrarows * stepsize)));
131                 cdp_num = (int) ceil (((double) span)
132                                 / ((double) (cdp_len * stepsize)));
134                 for (j = 0; j < rra_types_num; j++)
135                 {
136                         if (rra_num >= rra_max)
137                                 break;
139                         if (snprintf (buffer, sizeof (buffer), "RRA:%s:%3.1f:%u:%u",
140                                                 rra_types[j], xff,
141                                                 cdp_len, cdp_num) >= sizeof (buffer))
142                         {
143                                 syslog (LOG_ERR, "rra_get: Buffer would have been truncated.");
144                                 continue;
145                         }
147                         rra_def[rra_num++] = sstrdup (buffer);
148                 }
149         }
151 #if COLLECT_DEBUG
152         DBG ("rra_num = %i", rra_num);
153         for (i = 0; i < rra_num; i++)
154                 DBG ("  %s", rra_def[i]);
155 #endif
157         *ret = rra_def;
158         return (rra_num);
161 static void ds_free (int ds_num, char **ds_def)
163         int i;
165         for (i = 0; i < ds_num; i++)
166                 if (ds_def[i] != NULL)
167                         free (ds_def[i]);
168         free (ds_def);
171 static int ds_get (char ***ret, const data_set_t *ds)
173         char **ds_def;
174         int ds_num;
176         char min[32];
177         char max[32];
178         char buffer[128];
180         DBG ("ds->ds_num = %i", ds->ds_num);
182         ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
183         if (ds_def == NULL)
184         {
185                 syslog (LOG_ERR, "rrdtool plugin: malloc failed: %s",
186                                 strerror (errno));
187                 return (-1);
188         }
189         memset (ds_def, '\0', ds->ds_num * sizeof (char *));
191         for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
192         {
193                 data_source_t *d = ds->ds + ds_num;
194                 char *type;
195                 int status;
197                 ds_def[ds_num] = NULL;
199                 if (d->type == DS_TYPE_COUNTER)
200                         type = "COUNTER";
201                 else if (d->type == DS_TYPE_GAUGE)
202                         type = "GAUGE";
203                 else
204                 {
205                         syslog (LOG_ERR, "rrdtool plugin: Unknown DS type: %i",
206                                         d->type);
207                         break;
208                 }
210                 if (isnan (d->min))
211                 {
212                         strcpy (min, "U");
213                 }
214                 else
215                 {
216                         snprintf (min, sizeof (min), "%lf", d->min);
217                         min[sizeof (min) - 1] = '\0';
218                 }
220                 if (isnan (d->max))
221                 {
222                         strcpy (max, "U");
223                 }
224                 else
225                 {
226                         snprintf (max, sizeof (max), "%lf", d->max);
227                         max[sizeof (max) - 1] = '\0';
228                 }
230                 status = snprintf (buffer, sizeof (buffer),
231                                 "DS:%s:%s:%i:%s:%s",
232                                 d->name, type, heartbeat,
233                                 min, max);
234                 if ((status < 1) || (status >= sizeof (buffer)))
235                         break;
237                 ds_def[ds_num] = sstrdup (buffer);
238         } /* for ds_num = 0 .. ds->ds_num */
240 #if COLLECT_DEBUG
242         int i;
243         DBG ("ds_num = %i", ds_num);
244         for (i = 0; i < ds_num; i++)
245                 DBG ("  %s", ds_def[i]);
247 #endif
249         if (ds_num != ds->ds_num)
250         {
251                 ds_free (ds_num, ds_def);
252                 return (-1);
253         }
255         *ret = ds_def;
256         return (ds_num);
259 static int rrd_create_file (char *filename, const data_set_t *ds)
261         char **argv;
262         int argc;
263         char **rra_def;
264         int rra_num;
265         char **ds_def;
266         int ds_num;
267         int i, j;
268         char stepsize_str[16];
269         int status = 0;
271         if (check_create_dir (filename))
272                 return (-1);
274         if ((rra_num = rra_get (&rra_def)) < 1)
275         {
276                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate RRAs");
277                 return (-1);
278         }
280         if ((ds_num = ds_get (&ds_def, ds)) < 1)
281         {
282                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate DSes");
283                 return (-1);
284         }
286         argc = ds_num + rra_num + 4;
288         if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
289         {
290                 syslog (LOG_ERR, "rrd_create failed: %s", strerror (errno));
291                 return (-1);
292         }
294         status = snprintf (stepsize_str, sizeof (stepsize_str),
295                         "%i", stepsize);
296         if ((status < 1) || (status >= sizeof (stepsize_str)))
297         {
298                 syslog (LOG_ERR, "rrdtool plugin: snprintf failed.");
299                 return (-1);
300         }
302         argv[0] = "create";
303         argv[1] = filename;
304         argv[2] = "-s";
305         argv[3] = stepsize_str;
307         j = 4;
308         for (i = 0; i < ds_num; i++)
309                 argv[j++] = ds_def[i];
310         for (i = 0; i < rra_num; i++)
311                 argv[j++] = rra_def[i];
312         argv[j] = NULL;
314         optind = 0; /* bug in librrd? */
315         rrd_clear_error ();
316         if (rrd_create (argc, argv) == -1)
317         {
318                 syslog (LOG_ERR, "rrd_create failed: %s: %s", filename, rrd_get_error ());
319                 status = -1;
320         }
322         free (argv);
323         ds_free (ds_num, ds_def);
325         return (status);
328 static int value_list_to_string (char *buffer, int buffer_len,
329                 const data_set_t *ds, const value_list_t *vl)
331         int offset;
332         int status;
333         int i;
335         memset (buffer, '\0', sizeof (buffer_len));
337         status = snprintf (buffer, buffer_len, "%u", (unsigned int) vl->time);
338         if ((status < 1) || (status >= buffer_len))
339                 return (-1);
340         offset = status;
342         for (i = 0; i < ds->ds_num; i++)
343         {
344                 if ((ds->ds[i].type != DS_TYPE_COUNTER)
345                                 && (ds->ds[i].type != DS_TYPE_GAUGE))
346                         return (-1);
348                 if (ds->ds[i].type == DS_TYPE_COUNTER)
349                         status = snprintf (buffer + offset, buffer_len - offset,
350                                         ":%llu", vl->values[i].counter);
351                 else
352                         status = snprintf (buffer + offset, buffer_len - offset,
353                                         ":%lf", vl->values[i].gauge);
355                 if ((status < 1) || (status >= (buffer_len - offset)))
356                         return (-1);
358                 offset += status;
359         } /* for ds->ds_num */
361         return (0);
362 } /* int value_list_to_string */
364 static int value_list_to_filename (char *buffer, int buffer_len,
365                 const data_set_t *ds, const value_list_t *vl)
367         int offset = 0;
368         int status;
370         if (datadir != NULL)
371         {
372                 status = snprintf (buffer + offset, buffer_len - offset,
373                                 "%s/", datadir);
374                 if ((status < 1) || (status >= buffer_len - offset))
375                         return (-1);
376                 offset += status;
377         }
379         status = snprintf (buffer + offset, buffer_len - offset,
380                         "%s/", vl->host);
381         if ((status < 1) || (status >= buffer_len - offset))
382                 return (-1);
383         offset += status;
385         if (strlen (vl->plugin_instance) > 0)
386                 status = snprintf (buffer + offset, buffer_len - offset,
387                                 "%s-%s/", vl->plugin, vl->plugin_instance);
388         else
389                 status = snprintf (buffer + offset, buffer_len - offset,
390                                 "%s/", vl->plugin);
391         if ((status < 1) || (status >= buffer_len - offset))
392                 return (-1);
393         offset += status;
395         if (strlen (vl->type_instance) > 0)
396                 status = snprintf (buffer + offset, buffer_len - offset,
397                                 "%s-%s.rrd", ds->type, vl->type_instance);
398         else
399                 status = snprintf (buffer + offset, buffer_len - offset,
400                                 "%s.rrd", ds->type);
401         if ((status < 1) || (status >= buffer_len - offset))
402                 return (-1);
403         offset += status;
405         return (0);
406 } /* int value_list_to_filename */
408 static rrd_cache_t *rrd_cache_insert (const char *filename,
409                 const char *value)
411         rrd_cache_t *rc = NULL;
412         int new_rc = 0;
414         if (cache != NULL)
415                 avl_get (cache, filename, (void *) &rc);
417         if (rc == NULL)
418         {
419                 rc = (rrd_cache_t *) malloc (sizeof (rrd_cache_t));
420                 if (rc == NULL)
421                         return (NULL);
422                 rc->values_num = 0;
423                 rc->values = NULL;
424                 rc->first_value = 0;
425                 new_rc = 1;
426         }
428         rc->values = (char **) realloc ((void *) rc->values,
429                         (rc->values_num + 1) * sizeof (char *));
430         if (rc->values == NULL)
431         {
432                 syslog (LOG_ERR, "rrdtool plugin: realloc failed: %s",
433                                 strerror (errno));
434                 if (cache != NULL)
435                 {
436                         void *cache_key = NULL;
437                         avl_remove (cache, filename, &cache_key, NULL);
438                         sfree (cache_key);
439                 }
440                 free (rc);
441                 return (NULL);
442         }
444         rc->values[rc->values_num] = strdup (value);
445         if (rc->values[rc->values_num] != NULL)
446                 rc->values_num++;
448         if (rc->values_num == 1)
449                 rc->first_value = time (NULL);
451         /* Insert if this is the first value */
452         if ((cache != NULL) && (new_rc == 1))
453         {
454                 void *cache_key = strdup (filename);
456                 if (cache_key == NULL)
457                 {
458                         syslog (LOG_ERR, "rrdtool plugin: strdup failed: %s",
459                                         strerror (errno));
460                         sfree (rc->values[0]);
461                         sfree (rc->values);
462                         sfree (rc);
463                         return (NULL);
464                 }
466                 avl_insert (cache, cache_key, rc);
467         }
469         DBG ("rrd_cache_insert (%s, %s) = %p", filename, value, (void *) rc);
471         return (rc);
472 } /* rrd_cache_t *rrd_cache_insert */
474 static int rrd_write_cache_entry (const char *filename, rrd_cache_t *rc)
476         char **argv;
477         int    argc;
479         char *fn;
480         int status;
482         int i;
484         argc = rc->values_num + 2;
485         argv = (char **) malloc ((argc + 1) * sizeof (char *));
486         if (argv == NULL)
487                 return (-1);
489         fn = strdup (filename);
490         if (fn == NULL)
491         {
492                 free (argv);
493                 return (-1);
494         }
496         argv[0] = "update";
497         argv[1] = fn;
498         memcpy (argv + 2, rc->values, rc->values_num * sizeof (char *));
499         argv[argc] = NULL;
501         DBG ("rrd_update (argc = %i, argv = %p)", argc, (void *) argv);
503         optind = 0; /* bug in librrd? */
504         rrd_clear_error ();
505         status = rrd_update (argc, argv);
507         free (argv);
508         free (fn);
510         /* Free the value list of `rc' */
511         for (i = 0; i < rc->values_num; i++)
512                 free (rc->values[i]);
513         free (rc->values);
514         rc->values = NULL;
515         rc->values_num = 0;
517         if (status != 0)
518         {
519                 syslog (LOG_WARNING, "rrd_update failed: %s: %s",
520                                 filename, rrd_get_error ());
521                 return (-1);
522         }
524         return (0);
525 } /* int rrd_write_cache_entry */
527 static void rrd_cache_flush (int timeout)
529         rrd_cache_t *rc;
530         time_t       now;
532         char **keys = NULL;
533         int    keys_num = 0;
535         char *key;
536         avl_iterator_t *iter;
537         int i;
539         if (cache == NULL)
540                 return;
542         DBG ("Flushing cache, timeout = %i", timeout);
544         now = time (NULL);
546         /* Build a list of entries to be flushed */
547         iter = avl_get_iterator (cache);
548         while (avl_iterator_next (iter, (void *) &key, (void *) &rc) == 0)
549         {
550                 DBG ("key = %s; age = %i;", key, now - rc->first_value);
551                 if ((now - rc->first_value) >= timeout)
552                 {
553                         keys = (char **) realloc ((void *) keys,
554                                         (keys_num + 1) * sizeof (char *));
555                         if (keys == NULL)
556                         {
557                                 DBG ("realloc failed: %s", strerror (errno));
558                                 syslog (LOG_ERR, "rrdtool plugin: "
559                                                 "realloc failed: %s",
560                                                 strerror (errno));
561                                 avl_iterator_destroy (iter);
562                                 return;
563                         }
564                         keys[keys_num] = key;
565                         keys_num++;
566                 }
567         } /* while (avl_iterator_next) */
568         avl_iterator_destroy (iter);
569         
570         for (i = 0; i < keys_num; i++)
571         {
572                 if (avl_remove (cache, keys[i], (void *) &key, (void *) &rc) != 0)
573                 {
574                         DBG ("avl_remove (%s) failed.", keys[i]);
575                         continue;
576                 }
578                 rrd_write_cache_entry (keys[i], rc);
579                 /* rc's value-list is free's by `rrd_write_cache_entry' */
580                 sfree (rc);
581                 sfree (key);
582                 keys[i] = NULL;
583         } /* for (i = 0..keys_num) */
585         free (keys);
586         DBG ("Flushed %i value(s)", keys_num);
588         cache_flush_last = now;
589 } /* void rrd_cache_flush */
591 static int rrd_write (const data_set_t *ds, const value_list_t *vl)
593         struct stat  statbuf;
594         char         filename[512];
595         char         values[512];
596         rrd_cache_t *rc;
597         time_t       now;
599         if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
600                 return (-1);
602         if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
603                 return (-1);
605         if (stat (filename, &statbuf) == -1)
606         {
607                 if (errno == ENOENT)
608                 {
609                         if (rrd_create_file (filename, ds))
610                                 return (-1);
611                 }
612                 else
613                 {
614                         syslog (LOG_ERR, "stat(%s) failed: %s",
615                                         filename, strerror (errno));
616                         return (-1);
617                 }
618         }
619         else if (!S_ISREG (statbuf.st_mode))
620         {
621                 syslog (LOG_ERR, "stat(%s): Not a regular file!",
622                                 filename);
623                 return (-1);
624         }
626         rc = rrd_cache_insert (filename, values);
627         if (rc == NULL)
628                 return (-1);
630         if (cache == NULL)
631         {
632                 rrd_write_cache_entry (filename, rc);
633                 /* rc's value-list is free's by `rrd_write_cache_entry' */
634                 sfree (rc);
635                 return (0);
636         }
638         now = time (NULL);
640         DBG ("age (%s) = %i", filename, now - rc->first_value);
642         /* `rc' is not free'd here, because we'll likely reuse it. If not, then
643          * the next flush will remove this entry.  */
644         if ((now - rc->first_value) >= cache_timeout)
645                 rrd_write_cache_entry (filename, rc);
647         if ((now - cache_flush_last) >= cache_flush_timeout)
648                 rrd_cache_flush (cache_flush_timeout);
650         return (0);
651 } /* int rrd_write */
653 static int rrd_config (const char *key, const char *value)
655         if (strcasecmp ("CacheTimeout", key) == 0)
656         {
657                 int tmp = atoi (value);
658                 if (tmp < 0)
659                 {
660                         fprintf (stderr, "rrdtool: `CacheTimeout' must "
661                                         "be greater than 0.\n");
662                         return (1);
663                 }
664                 cache_timeout = tmp;
665         }
666         else if (strcasecmp ("CacheFlush", key) == 0)
667         {
668                 int tmp = atoi (value);
669                 if (tmp < 0)
670                 {
671                         fprintf (stderr, "rrdtool: `CacheFlush' must "
672                                         "be greater than 0.\n");
673                         return (1);
674                 }
675                 cache_flush_timeout = tmp;
676         }
677         else if (strcasecmp ("DataDir", key) == 0)
678         {
679                 if (datadir != NULL)
680                         free (datadir);
681                 datadir = strdup (value);
682                 if (datadir != NULL)
683                 {
684                         int len = strlen (datadir);
685                         while ((len > 0) && (datadir[len - 1] == '/'))
686                         {
687                                 len--;
688                                 datadir[len] = '\0';
689                         }
690                         if (len <= 0)
691                         {
692                                 free (datadir);
693                                 datadir = NULL;
694                         }
695                 }
696         }
697         else if (strcasecmp ("StepSize", key) == 0)
698         {
699                 int tmp = atoi (value);
700                 if (tmp <= 0)
701                 {
702                         fprintf (stderr, "rrdtool: `StepSize' must "
703                                         "be greater than 0.\n");
704                         return (1);
705                 }
706                 stepsize = tmp;
707         }
708         else if (strcasecmp ("HeartBeat", key) == 0)
709         {
710                 int tmp = atoi (value);
711                 if (tmp <= 0)
712                 {
713                         fprintf (stderr, "rrdtool: `HeartBeat' must "
714                                         "be greater than 0.\n");
715                         return (1);
716                 }
717                 heartbeat = tmp;
718         }
719         else if (strcasecmp ("RRARows", key) == 0)
720         {
721                 int tmp = atoi (value);
722                 if (tmp <= 0)
723                 {
724                         fprintf (stderr, "rrdtool: `RRARows' must "
725                                         "be greater than 0.\n");
726                         return (1);
727                 }
728                 rrarows = tmp;
729         }
730         else if (strcasecmp ("XFF", key) == 0)
731         {
732                 double tmp = atof (value);
733                 if ((tmp < 0.0) || (tmp >= 1.0))
734                 {
735                         fprintf (stderr, "rrdtool: `XFF' must "
736                                         "be in the range 0 to 1 (exclusive).");
737                         return (1);
738                 }
739                 xff = tmp;
740         }
741         else
742         {
743                 return (-1);
744         }
745         return (0);
746 } /* int rrd_config */
748 static int rrd_shutdown (void)
750         rrd_cache_flush (-1);
751         if (cache != NULL)
752                 avl_destroy (cache);
753         cache = NULL;
755         return (0);
756 } /* int rrd_shutdown */
758 static int rrd_init (void)
760         if (stepsize <= 0)
761                 stepsize = interval_g;
762         if (heartbeat <= 0)
763                 heartbeat = 2 * interval_g;
765         if (heartbeat < interval_g)
766                 syslog (LOG_WARNING, "rrdtool plugin: Your `heartbeat' is "
767                                 "smaller than your `interval'. This will "
768                                 "likely cause problems.");
769         else if (stepsize < interval_g)
770                 syslog (LOG_WARNING, "rrdtool plugin: Your `stepsize' is "
771                                 "smaller than your `interval'. This will "
772                                 "create needlessly big RRD-files.");
774         if (cache_timeout < 2)
775         {
776                 cache_timeout = 0;
777                 cache_flush_timeout = 0;
778         }
779         else
780         {
781                 if (cache_flush_timeout < cache_timeout)
782                         cache_flush_timeout = 10 * cache_timeout;
784                 cache = avl_create ((int (*) (const void *, const void *)) strcmp);
785                 cache_flush_last = time (NULL);
786                 plugin_register_shutdown ("rrdtool", rrd_shutdown);
787         }
789         DBG ("datadir = %s; stepsize = %i; heartbeat = %i; rrarows = %i; xff = %lf;",
790                         (datadir == NULL) ? "(null)" : datadir,
791                         stepsize, heartbeat, rrarows, xff);
793         return (0);
794 } /* int rrd_init */
796 void module_register (void)
798         plugin_register_config ("rrdtool", rrd_config,
799                         config_keys, config_keys_num);
800         plugin_register_init ("rrdtool", rrd_init);
801         plugin_register_write ("rrdtool", rrd_write);