src/plugin.c: Remove a legacy debug message.
[collectd.git] / src / rrdtool.c
1 /**
2  * collectd - src/rrdtool.c
3  * Copyright (C) 2006,2007  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 "plugin.h"
24 #include "common.h"
25 #include "utils_avltree.h"
26
27 #if HAVE_PTHREAD_H
28 # include <pthread.h>
29 #endif
30
31 /*
32  * Private types
33  */
34 struct rrd_cache_s
35 {
36         int    values_num;
37         char **values;
38         time_t first_value;
39         time_t last_value;
40 };
41 typedef struct rrd_cache_s rrd_cache_t;
42
43 /*
44  * Private variables
45  */
46 static int rra_timespans[] =
47 {
48         3600,
49         86400,
50         604800,
51         2678400,
52         31622400
53 };
54 static int rra_timespans_num = STATIC_ARRAY_SIZE (rra_timespans);
55
56 static int *rra_timespans_custom = NULL;
57 static int rra_timespans_custom_num = 0;
58
59 static char *rra_types[] =
60 {
61         "AVERAGE",
62         "MIN",
63         "MAX"
64 };
65 static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
66
67 static const char *config_keys[] =
68 {
69         "CacheTimeout",
70         "CacheFlush",
71         "DataDir",
72         "StepSize",
73         "HeartBeat",
74         "RRARows",
75         "RRATimespan",
76         "XFF"
77 };
78 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
79
80 static char   *datadir   = NULL;
81 static int     stepsize  = 0;
82 static int     heartbeat = 0;
83 static int     rrarows   = 1200;
84 static double  xff       = 0.1;
85
86 static int         cache_timeout = 0;
87 static int         cache_flush_timeout = 0;
88 static time_t      cache_flush_last;
89 static avl_tree_t *cache = NULL;
90 static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
91
92 /* * * * * * * * * *
93  * WARNING:  Magic *
94  * * * * * * * * * */
95 static int rra_get (char ***ret)
96 {
97         static char **rra_def = NULL;
98         static int rra_num = 0;
99
100         int *rts;
101         int  rts_num;
102
103         int rra_max;
104
105         int span;
106
107         int cdp_num;
108         int cdp_len;
109         int i, j;
110
111         char buffer[64];
112
113         if ((rra_num != 0) && (rra_def != NULL))
114         {
115                 *ret = rra_def;
116                 return (rra_num);
117         }
118
119         /* Use the configured timespans or fall back to the built-in defaults */
120         if (rra_timespans_custom_num != 0)
121         {
122                 rts = rra_timespans_custom;
123                 rts_num = rra_timespans_custom_num;
124         }
125         else
126         {
127                 rts = rra_timespans;
128                 rts_num = rra_timespans_num;
129         }
130
131         rra_max = rts_num * rra_types_num;
132
133         if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
134                 return (-1);
135         memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
136
137         if ((stepsize <= 0) || (rrarows <= 0))
138         {
139                 *ret = NULL;
140                 return (-1);
141         }
142
143         cdp_len = 0;
144         for (i = 0; i < rts_num; i++)
145         {
146                 span = rts[i];
147
148                 if ((span / stepsize) < rrarows)
149                         continue;
150
151                 if (cdp_len == 0)
152                         cdp_len = 1;
153                 else
154                         cdp_len = (int) floor (((double) span)
155                                         / ((double) (rrarows * stepsize)));
156
157                 cdp_num = (int) ceil (((double) span)
158                                 / ((double) (cdp_len * stepsize)));
159
160                 for (j = 0; j < rra_types_num; j++)
161                 {
162                         if (rra_num >= rra_max)
163                                 break;
164
165                         if (snprintf (buffer, sizeof (buffer), "RRA:%s:%3.1f:%u:%u",
166                                                 rra_types[j], xff,
167                                                 cdp_len, cdp_num) >= sizeof (buffer))
168                         {
169                                 ERROR ("rra_get: Buffer would have been truncated.");
170                                 continue;
171                         }
172
173                         rra_def[rra_num++] = sstrdup (buffer);
174                 }
175         }
176
177 #if COLLECT_DEBUG
178         DEBUG ("rra_num = %i", rra_num);
179         for (i = 0; i < rra_num; i++)
180                 DEBUG ("  %s", rra_def[i]);
181 #endif
182
183         *ret = rra_def;
184         return (rra_num);
185 }
186
187 static void ds_free (int ds_num, char **ds_def)
188 {
189         int i;
190
191         for (i = 0; i < ds_num; i++)
192                 if (ds_def[i] != NULL)
193                         free (ds_def[i]);
194         free (ds_def);
195 }
196
197 static int ds_get (char ***ret, const data_set_t *ds)
198 {
199         char **ds_def;
200         int ds_num;
201
202         char min[32];
203         char max[32];
204         char buffer[128];
205
206         DEBUG ("ds->ds_num = %i", ds->ds_num);
207
208         ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
209         if (ds_def == NULL)
210         {
211                 char errbuf[1024];
212                 ERROR ("rrdtool plugin: malloc failed: %s",
213                                 sstrerror (errno, errbuf, sizeof (errbuf)));
214                 return (-1);
215         }
216         memset (ds_def, '\0', ds->ds_num * sizeof (char *));
217
218         for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
219         {
220                 data_source_t *d = ds->ds + ds_num;
221                 char *type;
222                 int status;
223
224                 ds_def[ds_num] = NULL;
225
226                 if (d->type == DS_TYPE_COUNTER)
227                         type = "COUNTER";
228                 else if (d->type == DS_TYPE_GAUGE)
229                         type = "GAUGE";
230                 else
231                 {
232                         ERROR ("rrdtool plugin: Unknown DS type: %i",
233                                         d->type);
234                         break;
235                 }
236
237                 if (isnan (d->min))
238                 {
239                         strcpy (min, "U");
240                 }
241                 else
242                 {
243                         snprintf (min, sizeof (min), "%lf", d->min);
244                         min[sizeof (min) - 1] = '\0';
245                 }
246
247                 if (isnan (d->max))
248                 {
249                         strcpy (max, "U");
250                 }
251                 else
252                 {
253                         snprintf (max, sizeof (max), "%lf", d->max);
254                         max[sizeof (max) - 1] = '\0';
255                 }
256
257                 status = snprintf (buffer, sizeof (buffer),
258                                 "DS:%s:%s:%i:%s:%s",
259                                 d->name, type, heartbeat,
260                                 min, max);
261                 if ((status < 1) || (status >= sizeof (buffer)))
262                         break;
263
264                 ds_def[ds_num] = sstrdup (buffer);
265         } /* for ds_num = 0 .. ds->ds_num */
266
267 #if COLLECT_DEBUG
268 {
269         int i;
270         DEBUG ("ds_num = %i", ds_num);
271         for (i = 0; i < ds_num; i++)
272                 DEBUG ("  %s", ds_def[i]);
273 }
274 #endif
275
276         if (ds_num != ds->ds_num)
277         {
278                 ds_free (ds_num, ds_def);
279                 return (-1);
280         }
281
282         *ret = ds_def;
283         return (ds_num);
284 }
285
286 static int rrd_create_file (char *filename, const data_set_t *ds)
287 {
288         char **argv;
289         int argc;
290         char **rra_def;
291         int rra_num;
292         char **ds_def;
293         int ds_num;
294         int i, j;
295         char stepsize_str[16];
296         int status = 0;
297
298         if (check_create_dir (filename))
299                 return (-1);
300
301         if ((rra_num = rra_get (&rra_def)) < 1)
302         {
303                 ERROR ("rrd_create_file failed: Could not calculate RRAs");
304                 return (-1);
305         }
306
307         if ((ds_num = ds_get (&ds_def, ds)) < 1)
308         {
309                 ERROR ("rrd_create_file failed: Could not calculate DSes");
310                 return (-1);
311         }
312
313         argc = ds_num + rra_num + 4;
314
315         if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
316         {
317                 char errbuf[1024];
318                 ERROR ("rrd_create failed: %s",
319                                 sstrerror (errno, errbuf, sizeof (errbuf)));
320                 return (-1);
321         }
322
323         status = snprintf (stepsize_str, sizeof (stepsize_str),
324                         "%i", stepsize);
325         if ((status < 1) || (status >= sizeof (stepsize_str)))
326         {
327                 ERROR ("rrdtool plugin: snprintf failed.");
328                 return (-1);
329         }
330
331         argv[0] = "create";
332         argv[1] = filename;
333         argv[2] = "-s";
334         argv[3] = stepsize_str;
335
336         j = 4;
337         for (i = 0; i < ds_num; i++)
338                 argv[j++] = ds_def[i];
339         for (i = 0; i < rra_num; i++)
340                 argv[j++] = rra_def[i];
341         argv[j] = NULL;
342
343         optind = 0; /* bug in librrd? */
344         rrd_clear_error ();
345         if (rrd_create (argc, argv) == -1)
346         {
347                 ERROR ("rrd_create failed: %s: %s", filename, rrd_get_error ());
348                 status = -1;
349         }
350
351         free (argv);
352         ds_free (ds_num, ds_def);
353
354         return (status);
355 }
356
357 static int value_list_to_string (char *buffer, int buffer_len,
358                 const data_set_t *ds, const value_list_t *vl)
359 {
360         int offset;
361         int status;
362         int i;
363
364         memset (buffer, '\0', buffer_len);
365
366         status = snprintf (buffer, buffer_len, "%u", (unsigned int) vl->time);
367         if ((status < 1) || (status >= buffer_len))
368                 return (-1);
369         offset = status;
370
371         for (i = 0; i < ds->ds_num; i++)
372         {
373                 if ((ds->ds[i].type != DS_TYPE_COUNTER)
374                                 && (ds->ds[i].type != DS_TYPE_GAUGE))
375                         return (-1);
376
377                 if (ds->ds[i].type == DS_TYPE_COUNTER)
378                         status = snprintf (buffer + offset, buffer_len - offset,
379                                         ":%llu", vl->values[i].counter);
380                 else
381                         status = snprintf (buffer + offset, buffer_len - offset,
382                                         ":%lf", vl->values[i].gauge);
383
384                 if ((status < 1) || (status >= (buffer_len - offset)))
385                         return (-1);
386
387                 offset += status;
388         } /* for ds->ds_num */
389
390         return (0);
391 } /* int value_list_to_string */
392
393 static int value_list_to_filename (char *buffer, int buffer_len,
394                 const data_set_t *ds, const value_list_t *vl)
395 {
396         int offset = 0;
397         int status;
398
399         if (datadir != NULL)
400         {
401                 status = snprintf (buffer + offset, buffer_len - offset,
402                                 "%s/", datadir);
403                 if ((status < 1) || (status >= buffer_len - offset))
404                         return (-1);
405                 offset += status;
406         }
407
408         status = snprintf (buffer + offset, buffer_len - offset,
409                         "%s/", vl->host);
410         if ((status < 1) || (status >= buffer_len - offset))
411                 return (-1);
412         offset += status;
413
414         if (strlen (vl->plugin_instance) > 0)
415                 status = snprintf (buffer + offset, buffer_len - offset,
416                                 "%s-%s/", vl->plugin, vl->plugin_instance);
417         else
418                 status = snprintf (buffer + offset, buffer_len - offset,
419                                 "%s/", vl->plugin);
420         if ((status < 1) || (status >= buffer_len - offset))
421                 return (-1);
422         offset += status;
423
424         if (strlen (vl->type_instance) > 0)
425                 status = snprintf (buffer + offset, buffer_len - offset,
426                                 "%s-%s.rrd", ds->type, vl->type_instance);
427         else
428                 status = snprintf (buffer + offset, buffer_len - offset,
429                                 "%s.rrd", ds->type);
430         if ((status < 1) || (status >= buffer_len - offset))
431                 return (-1);
432         offset += status;
433
434         return (0);
435 } /* int value_list_to_filename */
436
437 static rrd_cache_t *rrd_cache_insert (const char *filename,
438                 const char *value, time_t value_time)
439 {
440         rrd_cache_t *rc = NULL;
441         int new_rc = 0;
442
443         if (cache != NULL)
444                 avl_get (cache, filename, (void *) &rc);
445
446         if (rc == NULL)
447         {
448                 rc = (rrd_cache_t *) malloc (sizeof (rrd_cache_t));
449                 if (rc == NULL)
450                         return (NULL);
451                 rc->values_num = 0;
452                 rc->values = NULL;
453                 rc->first_value = 0;
454                 rc->last_value = 0;
455                 new_rc = 1;
456         }
457
458         if (rc->last_value >= value_time)
459         {
460                 WARNING ("rrdtool plugin: (rc->last_value = %u) >= (value_time = %u)",
461                                 (unsigned int) rc->last_value,
462                                 (unsigned int) value_time);
463                 return (NULL);
464         }
465
466         rc->values = (char **) realloc ((void *) rc->values,
467                         (rc->values_num + 1) * sizeof (char *));
468         if (rc->values == NULL)
469         {
470                 char errbuf[1024];
471                 ERROR ("rrdtool plugin: realloc failed: %s",
472                                 sstrerror (errno, errbuf, sizeof (errbuf)));
473                 if (cache != NULL)
474                 {
475                         void *cache_key = NULL;
476                         avl_remove (cache, filename, &cache_key, NULL);
477                         sfree (cache_key);
478                 }
479                 free (rc);
480                 return (NULL);
481         }
482
483         rc->values[rc->values_num] = strdup (value);
484         if (rc->values[rc->values_num] != NULL)
485                 rc->values_num++;
486
487         if (rc->values_num == 1)
488                 rc->first_value = value_time;
489         rc->last_value = value_time;
490
491         /* Insert if this is the first value */
492         if ((cache != NULL) && (new_rc == 1))
493         {
494                 void *cache_key = strdup (filename);
495
496                 if (cache_key == NULL)
497                 {
498                         char errbuf[1024];
499                         ERROR ("rrdtool plugin: strdup failed: %s",
500                                         sstrerror (errno, errbuf, sizeof (errbuf)));
501                         sfree (rc->values[0]);
502                         sfree (rc->values);
503                         sfree (rc);
504                         return (NULL);
505                 }
506
507                 avl_insert (cache, cache_key, rc);
508         }
509
510         DEBUG ("rrd_cache_insert (%s, %s, %u) = %p", filename, value,
511                         (unsigned int) value_time, (void *) rc);
512
513         return (rc);
514 } /* rrd_cache_t *rrd_cache_insert */
515
516 static int rrd_write_cache_entry (const char *filename, rrd_cache_t *rc)
517 {
518         char **argv;
519         int    argc;
520
521         char *fn;
522         int status;
523
524         int i;
525
526         if (rc->values_num < 1)
527                 return (0);
528
529         argc = rc->values_num + 2;
530         argv = (char **) malloc ((argc + 1) * sizeof (char *));
531         if (argv == NULL)
532                 return (-1);
533
534         fn = strdup (filename);
535         if (fn == NULL)
536         {
537                 free (argv);
538                 return (-1);
539         }
540
541         argv[0] = "update";
542         argv[1] = fn;
543         memcpy (argv + 2, rc->values, rc->values_num * sizeof (char *));
544         argv[argc] = NULL;
545
546         DEBUG ("rrd_update (argc = %i, argv = %p)", argc, (void *) argv);
547
548         optind = 0; /* bug in librrd? */
549         rrd_clear_error ();
550         status = rrd_update (argc, argv);
551         if (status != 0)
552         {
553                 WARNING ("rrd_update failed: %s: %s",
554                                 filename, rrd_get_error ());
555                 status = -1;
556         }
557
558         free (argv);
559         free (fn);
560         /* Free the value list of `rc' */
561         for (i = 0; i < rc->values_num; i++)
562                 free (rc->values[i]);
563         free (rc->values);
564         rc->values = NULL;
565         rc->values_num = 0;
566
567         return (status);
568 } /* int rrd_write_cache_entry */
569
570 static void rrd_cache_flush (int timeout)
571 {
572         rrd_cache_t *rc;
573         time_t       now;
574
575         char **keys = NULL;
576         int    keys_num = 0;
577
578         char *key;
579         avl_iterator_t *iter;
580         int i;
581
582         if (cache == NULL)
583                 return;
584
585         DEBUG ("Flushing cache, timeout = %i", timeout);
586
587         now = time (NULL);
588
589         /* Build a list of entries to be flushed */
590         iter = avl_get_iterator (cache);
591         while (avl_iterator_next (iter, (void *) &key, (void *) &rc) == 0)
592         {
593                 DEBUG ("key = %s; age = %i;", key, now - rc->first_value);
594                 if ((now - rc->first_value) >= timeout)
595                 {
596                         keys = (char **) realloc ((void *) keys,
597                                         (keys_num + 1) * sizeof (char *));
598                         if (keys == NULL)
599                         {
600                                 char errbuf[1024];
601                                 ERROR ("rrdtool plugin: "
602                                                 "realloc failed: %s",
603                                                 sstrerror (errno, errbuf,
604                                                         sizeof (errbuf)));
605                                 avl_iterator_destroy (iter);
606                                 return;
607                         }
608                         keys[keys_num] = key;
609                         keys_num++;
610                 }
611         } /* while (avl_iterator_next) */
612         avl_iterator_destroy (iter);
613         
614         for (i = 0; i < keys_num; i++)
615         {
616                 if (avl_remove (cache, keys[i], (void *) &key, (void *) &rc) != 0)
617                 {
618                         DEBUG ("avl_remove (%s) failed.", keys[i]);
619                         continue;
620                 }
621
622                 rrd_write_cache_entry (keys[i], rc);
623                 /* rc's value-list is free's by `rrd_write_cache_entry' */
624                 sfree (rc);
625                 sfree (key);
626                 keys[i] = NULL;
627         } /* for (i = 0..keys_num) */
628
629         free (keys);
630         DEBUG ("Flushed %i value(s)", keys_num);
631
632         cache_flush_last = now;
633 } /* void rrd_cache_flush */
634
635 static int rrd_write (const data_set_t *ds, const value_list_t *vl)
636 {
637         struct stat  statbuf;
638         char         filename[512];
639         char         values[512];
640         rrd_cache_t *rc;
641         time_t       now;
642
643         if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
644                 return (-1);
645
646         if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
647                 return (-1);
648
649         if (stat (filename, &statbuf) == -1)
650         {
651                 if (errno == ENOENT)
652                 {
653                         if (rrd_create_file (filename, ds))
654                                 return (-1);
655                 }
656                 else
657                 {
658                         char errbuf[1024];
659                         ERROR ("stat(%s) failed: %s", filename,
660                                         sstrerror (errno, errbuf,
661                                                 sizeof (errbuf)));
662                         return (-1);
663                 }
664         }
665         else if (!S_ISREG (statbuf.st_mode))
666         {
667                 ERROR ("stat(%s): Not a regular file!",
668                                 filename);
669                 return (-1);
670         }
671
672         pthread_mutex_lock (&cache_lock);
673         rc = rrd_cache_insert (filename, values, vl->time);
674         if (rc == NULL)
675         {
676                 pthread_mutex_unlock (&cache_lock);
677                 return (-1);
678         }
679
680         if (cache == NULL)
681         {
682                 rrd_write_cache_entry (filename, rc);
683                 /* rc's value-list is free's by `rrd_write_cache_entry' */
684                 sfree (rc);
685                 pthread_mutex_unlock (&cache_lock);
686                 return (0);
687         }
688
689         now = time (NULL);
690
691         DEBUG ("age (%s) = %i", filename, now - rc->first_value);
692
693         /* `rc' is not free'd here, because we'll likely reuse it. If not, then
694          * the next flush will remove this entry.  */
695         if ((now - rc->first_value) >= cache_timeout)
696                 rrd_write_cache_entry (filename, rc);
697
698         if ((now - cache_flush_last) >= cache_flush_timeout)
699                 rrd_cache_flush (cache_flush_timeout);
700
701         pthread_mutex_unlock (&cache_lock);
702         return (0);
703 } /* int rrd_write */
704
705 static int rrd_config (const char *key, const char *value)
706 {
707         if (strcasecmp ("CacheTimeout", key) == 0)
708         {
709                 int tmp = atoi (value);
710                 if (tmp < 0)
711                 {
712                         fprintf (stderr, "rrdtool: `CacheTimeout' must "
713                                         "be greater than 0.\n");
714                         return (1);
715                 }
716                 cache_timeout = tmp;
717         }
718         else if (strcasecmp ("CacheFlush", key) == 0)
719         {
720                 int tmp = atoi (value);
721                 if (tmp < 0)
722                 {
723                         fprintf (stderr, "rrdtool: `CacheFlush' must "
724                                         "be greater than 0.\n");
725                         return (1);
726                 }
727                 cache_flush_timeout = tmp;
728         }
729         else if (strcasecmp ("DataDir", key) == 0)
730         {
731                 if (datadir != NULL)
732                         free (datadir);
733                 datadir = strdup (value);
734                 if (datadir != NULL)
735                 {
736                         int len = strlen (datadir);
737                         while ((len > 0) && (datadir[len - 1] == '/'))
738                         {
739                                 len--;
740                                 datadir[len] = '\0';
741                         }
742                         if (len <= 0)
743                         {
744                                 free (datadir);
745                                 datadir = NULL;
746                         }
747                 }
748         }
749         else if (strcasecmp ("StepSize", key) == 0)
750         {
751                 int tmp = atoi (value);
752                 if (tmp <= 0)
753                 {
754                         fprintf (stderr, "rrdtool: `StepSize' must "
755                                         "be greater than 0.\n");
756                         return (1);
757                 }
758                 stepsize = tmp;
759         }
760         else if (strcasecmp ("HeartBeat", key) == 0)
761         {
762                 int tmp = atoi (value);
763                 if (tmp <= 0)
764                 {
765                         fprintf (stderr, "rrdtool: `HeartBeat' must "
766                                         "be greater than 0.\n");
767                         return (1);
768                 }
769                 heartbeat = tmp;
770         }
771         else if (strcasecmp ("RRARows", key) == 0)
772         {
773                 int tmp = atoi (value);
774                 if (tmp <= 0)
775                 {
776                         fprintf (stderr, "rrdtool: `RRARows' must "
777                                         "be greater than 0.\n");
778                         return (1);
779                 }
780                 rrarows = tmp;
781         }
782         else if (strcasecmp ("RRATimespan", key) == 0)
783         {
784                 char *saveptr = NULL;
785                 char *dummy;
786                 char *ptr;
787                 char *value_copy;
788                 int *tmp_alloc;
789
790                 value_copy = strdup (value);
791                 if (value_copy == NULL)
792                         return (1);
793
794                 dummy = value_copy;
795                 while ((ptr = strtok_r (dummy, ", \t", &saveptr)) != NULL)
796                 {
797                         dummy = NULL;
798                         
799                         tmp_alloc = realloc (rra_timespans_custom,
800                                         sizeof (int) * (rra_timespans_custom_num + 1));
801                         if (tmp_alloc == NULL)
802                         {
803                                 fprintf (stderr, "rrdtool: realloc failed.\n");
804                                 free (value_copy);
805                                 return (1);
806                         }
807                         rra_timespans_custom = tmp_alloc;
808                         rra_timespans_custom[rra_timespans_custom_num] = atoi (ptr);
809                         if (rra_timespans_custom[rra_timespans_custom_num] != 0)
810                                 rra_timespans_custom_num++;
811                 } /* while (strtok_r) */
812                 free (value_copy);
813         }
814         else if (strcasecmp ("XFF", key) == 0)
815         {
816                 double tmp = atof (value);
817                 if ((tmp < 0.0) || (tmp >= 1.0))
818                 {
819                         fprintf (stderr, "rrdtool: `XFF' must "
820                                         "be in the range 0 to 1 (exclusive).");
821                         return (1);
822                 }
823                 xff = tmp;
824         }
825         else
826         {
827                 return (-1);
828         }
829         return (0);
830 } /* int rrd_config */
831
832 static int rrd_shutdown (void)
833 {
834         pthread_mutex_lock (&cache_lock);
835         rrd_cache_flush (-1);
836         if (cache != NULL)
837                 avl_destroy (cache);
838         cache = NULL;
839         pthread_mutex_unlock (&cache_lock);
840
841         return (0);
842 } /* int rrd_shutdown */
843
844 static int rrd_init (void)
845 {
846         if (stepsize <= 0)
847                 stepsize = interval_g;
848         if (heartbeat <= 0)
849                 heartbeat = 2 * interval_g;
850
851         if (heartbeat < interval_g)
852                 WARNING ("rrdtool plugin: Your `heartbeat' is "
853                                 "smaller than your `interval'. This will "
854                                 "likely cause problems.");
855         else if (stepsize < interval_g)
856                 WARNING ("rrdtool plugin: Your `stepsize' is "
857                                 "smaller than your `interval'. This will "
858                                 "create needlessly big RRD-files.");
859
860         pthread_mutex_lock (&cache_lock);
861         if (cache_timeout < 2)
862         {
863                 cache_timeout = 0;
864                 cache_flush_timeout = 0;
865         }
866         else
867         {
868                 if (cache_flush_timeout < cache_timeout)
869                         cache_flush_timeout = 10 * cache_timeout;
870
871                 cache = avl_create ((int (*) (const void *, const void *)) strcmp);
872                 cache_flush_last = time (NULL);
873                 plugin_register_shutdown ("rrdtool", rrd_shutdown);
874         }
875         pthread_mutex_unlock (&cache_lock);
876
877         DEBUG ("rrdtool plugin: rrd_init: datadir = %s; stepsize = %i;"
878                         " heartbeat = %i; rrarows = %i; xff = %lf;",
879                         (datadir == NULL) ? "(null)" : datadir,
880                         stepsize, heartbeat, rrarows, xff);
881
882         return (0);
883 } /* int rrd_init */
884
885 void module_register (void)
886 {
887         plugin_register_config ("rrdtool", rrd_config,
888                         config_keys, config_keys_num);
889         plugin_register_init ("rrdtool", rrd_init);
890         plugin_register_write ("rrdtool", rrd_write);
891 }