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