rrdtool plugin: Implemented a `DataDir' config option to be able to store the RRD...
[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_llist.h"
26 #include "utils_debug.h"
27
28 /*
29  * This weird macro cascade forces the glibc to define `NAN'. I don't know
30  * another way to solve this, so more intelligent solutions are welcome. -octo
31  */
32 #ifndef __USE_ISOC99
33 # define DISABLE__USE_ISOC99 1
34 # define __USE_ISOC99 1
35 #endif
36 #include <math.h>
37 #ifdef DISABLE__USE_ISOC99
38 # undef DISABLE__USE_ISOC99
39 # undef __USE_ISOC99
40 #endif
41
42 /*
43  * Private types
44  */
45 struct rrd_cache_s
46 {
47         int    values_num;
48         char **values;
49         time_t first_value;
50 };
51 typedef struct rrd_cache_s rrd_cache_t;
52
53 /*
54  * Private variables
55  */
56 static int rra_timespans[] =
57 {
58         3600,
59         86400,
60         604800,
61         2678400,
62         31622400,
63         0
64 };
65 static int rra_timespans_num = 5;
66
67 static char *rra_types[] =
68 {
69         "AVERAGE",
70         "MIN",
71         "MAX",
72         NULL
73 };
74 static int rra_types_num = 3;
75
76 static const char *config_keys[] =
77 {
78         "CacheTimeout",
79         "DataDir",
80         NULL
81 };
82 static int config_keys_num = 2;
83
84 static char *datadir = NULL;
85
86 static int      cache_timeout = 0;
87 static time_t   cache_flush;
88 static llist_t *cache = NULL;
89
90 /* * * * * * * * * *
91  * WARNING:  Magic *
92  * * * * * * * * * */
93 static int rra_get (char ***ret)
94 {
95         static char **rra_def = NULL;
96         static int rra_num = 0;
97
98         int rra_max = rra_timespans_num * rra_types_num;
99
100         int step;
101         int rows;
102         int span;
103
104         int cdp_num;
105         int cdp_len;
106         int i, j;
107
108         char buffer[64];
109
110         if ((rra_num != 0) && (rra_def != NULL))
111         {
112                 *ret = rra_def;
113                 return (rra_num);
114         }
115
116         if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
117                 return (-1);
118         memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
119
120         step = atoi (COLLECTD_STEP);
121         rows = atoi (COLLECTD_ROWS);
122
123         if ((step <= 0) || (rows <= 0))
124         {
125                 *ret = NULL;
126                 return (-1);
127         }
128
129         cdp_len = 0;
130         for (i = 0; i < rra_timespans_num; i++)
131         {
132                 span = rra_timespans[i];
133
134                 if ((span / step) < rows)
135                         continue;
136
137                 if (cdp_len == 0)
138                         cdp_len = 1;
139                 else
140                         cdp_len = (int) floor (((double) span) / ((double) (rows * step)));
141
142                 cdp_num = (int) ceil (((double) span) / ((double) (cdp_len * step)));
143
144                 for (j = 0; j < rra_types_num; j++)
145                 {
146                         if (rra_num >= rra_max)
147                                 break;
148
149                         if (snprintf (buffer, sizeof(buffer), "RRA:%s:%3.1f:%u:%u",
150                                                 rra_types[j], COLLECTD_XFF,
151                                                 cdp_len, cdp_num) >= sizeof (buffer))
152                         {
153                                 syslog (LOG_ERR, "rra_get: Buffer would have been truncated.");
154                                 continue;
155                         }
156
157                         rra_def[rra_num++] = sstrdup (buffer);
158                 }
159         }
160
161 #if COLLECT_DEBUG
162         DBG ("rra_num = %i", rra_num);
163         for (i = 0; i < rra_num; i++)
164                 DBG ("  %s", rra_def[i]);
165 #endif
166
167         *ret = rra_def;
168         return (rra_num);
169 }
170
171 static void ds_free (int ds_num, char **ds_def)
172 {
173         int i;
174
175         for (i = 0; i < ds_num; i++)
176                 if (ds_def[i] != NULL)
177                         free (ds_def[i]);
178         free (ds_def);
179 }
180
181 static int ds_get (char ***ret, const data_set_t *ds)
182 {
183         char **ds_def;
184         int ds_num;
185
186         char min[32];
187         char max[32];
188         char buffer[128];
189
190         DBG ("ds->ds_num = %i", ds->ds_num);
191
192         ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
193         if (ds_def == NULL)
194         {
195                 syslog (LOG_ERR, "rrdtool plugin: malloc failed: %s",
196                                 strerror (errno));
197                 return (-1);
198         }
199         memset (ds_def, '\0', ds->ds_num * sizeof (char *));
200
201         for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
202         {
203                 data_source_t *d = ds->ds + ds_num;
204                 char *type;
205                 int status;
206
207                 ds_def[ds_num] = NULL;
208
209                 if (d->type == DS_TYPE_COUNTER)
210                         type = "COUNTER";
211                 else if (d->type == DS_TYPE_GAUGE)
212                         type = "GAUGE";
213                 else
214                 {
215                         syslog (LOG_ERR, "rrdtool plugin: Unknown DS type: %i",
216                                         d->type);
217                         break;
218                 }
219
220                 if (d->min == NAN)
221                 {
222                         strcpy (min, "U");
223                 }
224                 else
225                 {
226                         snprintf (min, sizeof (min), "%lf", d->min);
227                         min[sizeof (min) - 1] = '\0';
228                 }
229
230                 if (d->max == NAN)
231                 {
232                         strcpy (max, "U");
233                 }
234                 else
235                 {
236                         snprintf (max, sizeof (max), "%lf", d->max);
237                         max[sizeof (max) - 1] = '\0';
238                 }
239
240                 status = snprintf (buffer, sizeof (buffer),
241                                 "DS:%s:%s:%s:%s:%s",
242                                 d->name, type, COLLECTD_HEARTBEAT,
243                                 min, max);
244                 if ((status < 1) || (status >= sizeof (buffer)))
245                         break;
246
247                 ds_def[ds_num] = sstrdup (buffer);
248         } /* for ds_num = 0 .. ds->ds_num */
249
250 #if COLLECT_DEBUG
251 {
252         int i;
253         DBG ("ds_num = %i", ds_num);
254         for (i = 0; i < ds_num; i++)
255                 DBG ("  %s", ds_def[i]);
256 }
257 #endif
258
259         if (ds_num != ds->ds_num)
260         {
261                 ds_free (ds_num, ds_def);
262                 return (-1);
263         }
264
265         *ret = ds_def;
266         return (ds_num);
267 }
268
269 static int rrd_create_file (char *filename, const data_set_t *ds)
270 {
271         char **argv;
272         int argc;
273         char **rra_def;
274         int rra_num;
275         char **ds_def;
276         int ds_num;
277         int i, j;
278         int status = 0;
279
280         if (check_create_dir (filename))
281                 return (-1);
282
283         if ((rra_num = rra_get (&rra_def)) < 1)
284         {
285                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate RRAs");
286                 return (-1);
287         }
288
289         if ((ds_num = ds_get (&ds_def, ds)) < 1)
290         {
291                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate DSes");
292                 return (-1);
293         }
294
295         argc = ds_num + rra_num + 4;
296
297         if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
298         {
299                 syslog (LOG_ERR, "rrd_create failed: %s", strerror (errno));
300                 return (-1);
301         }
302
303         argv[0] = "create";
304         argv[1] = filename;
305         argv[2] = "-s";
306         argv[3] = COLLECTD_STEP;
307
308         j = 4;
309         for (i = 0; i < ds_num; i++)
310                 argv[j++] = ds_def[i];
311         for (i = 0; i < rra_num; i++)
312                 argv[j++] = rra_def[i];
313         argv[j] = NULL;
314
315         optind = 0; /* bug in librrd? */
316         rrd_clear_error ();
317         if (rrd_create (argc, argv) == -1)
318         {
319                 syslog (LOG_ERR, "rrd_create failed: %s: %s", filename, rrd_get_error ());
320                 status = -1;
321         }
322
323         free (argv);
324         ds_free (ds_num, ds_def);
325
326         return (status);
327 }
328
329 static int value_list_to_string (char *buffer, int buffer_len,
330                 const data_set_t *ds, const value_list_t *vl)
331 {
332         int offset;
333         int status;
334         int i;
335
336         memset (buffer, '\0', sizeof (buffer_len));
337
338         status = snprintf (buffer, buffer_len, "%u", (unsigned int) vl->time);
339         if ((status < 1) || (status >= buffer_len))
340                 return (-1);
341         offset = status;
342
343         for (i = 0; i < ds->ds_num; i++)
344         {
345                 if ((ds->ds[i].type != DS_TYPE_COUNTER)
346                                 && (ds->ds[i].type != DS_TYPE_GAUGE))
347                         return (-1);
348
349                 if (ds->ds[i].type == DS_TYPE_COUNTER)
350                         status = snprintf (buffer + offset, buffer_len - offset,
351                                         ":%llu", vl->values[i].counter);
352                 else
353                         status = snprintf (buffer + offset, buffer_len - offset,
354                                         ":%lf", vl->values[i].gauge);
355
356                 if ((status < 1) || (status >= (buffer_len - offset)))
357                         return (-1);
358
359                 offset += status;
360         } /* for ds->ds_num */
361
362         return (0);
363 } /* int value_list_to_string */
364
365 static int value_list_to_filename (char *buffer, int buffer_len,
366                 const data_set_t *ds, const value_list_t *vl)
367 {
368         int offset = 0;
369         int status;
370
371         if (datadir != NULL)
372         {
373                 status = snprintf (buffer + offset, buffer_len - offset,
374                                 "%s/", datadir);
375                 if ((status < 1) || (status >= buffer_len - offset))
376                         return (-1);
377                 offset += status;
378         }
379
380         status = snprintf (buffer + offset, buffer_len - offset,
381                         "%s/", vl->host);
382         if ((status < 1) || (status >= buffer_len - offset))
383                 return (-1);
384         offset += status;
385
386         if (strlen (vl->plugin_instance) > 0)
387                 status = snprintf (buffer + offset, buffer_len - offset,
388                                 "%s-%s/", vl->plugin, vl->plugin_instance);
389         else
390                 status = snprintf (buffer + offset, buffer_len - offset,
391                                 "%s/", vl->plugin);
392         if ((status < 1) || (status >= buffer_len - offset))
393                 return (-1);
394         offset += status;
395
396         if (strlen (vl->type_instance) > 0)
397                 status = snprintf (buffer + offset, buffer_len - offset,
398                                 "%s-%s.rrd", ds->type, vl->type_instance);
399         else
400                 status = snprintf (buffer + offset, buffer_len - offset,
401                                 "%s.rrd", ds->type);
402         if ((status < 1) || (status >= buffer_len - offset))
403                 return (-1);
404         offset += status;
405
406         return (0);
407 } /* int value_list_to_filename */
408
409 static rrd_cache_t *rrd_cache_insert (const char *filename,
410                 const char *value)
411 {
412         rrd_cache_t *rc = NULL;
413         llentry_t   *le = NULL;
414
415         if (cache != NULL)
416         {
417                 le = llist_search (cache, filename);
418                 if (le != NULL)
419                         rc = (rrd_cache_t *) le->value;
420         }
421
422         if (rc == NULL)
423         {
424                 rc = (rrd_cache_t *) malloc (sizeof (rrd_cache_t));
425                 if (rc == NULL)
426                         return (NULL);
427                 rc->values_num = 0;
428                 rc->values = NULL;
429                 rc->first_value = 0;
430         }
431
432         rc->values = (char **) realloc ((void *) rc->values,
433                         (rc->values_num + 1) * sizeof (char *));
434         if (rc->values == NULL)
435         {
436                 syslog (LOG_ERR, "rrdtool plugin: realloc failed: %s",
437                                 strerror (errno));
438                 free (rc);
439                 if (le != NULL)
440                 {
441                         llist_remove (cache, le);
442                         llentry_destroy (le);
443                 }
444                 return (NULL);
445         }
446
447         rc->values[rc->values_num] = strdup (value);
448         if (rc->values[rc->values_num] != NULL)
449                 rc->values_num++;
450
451         if (rc->values_num == 1)
452                 rc->first_value = time (NULL);
453
454         if ((cache != NULL) && (le == NULL))
455         {
456                 le = llentry_create (filename, (void *) rc);
457                 if (le != NULL)
458                         llist_prepend (cache, le);
459         }
460
461         DBG ("rrd_cache_insert (%s, %s) = %p", filename, value, (void *) rc);
462
463         return (rc);
464 } /* rrd_cache_t *rrd_cache_insert */
465
466 static int rrd_write_cache_entry (const char *filename, rrd_cache_t *rc)
467 {
468         char **argv;
469         int    argc;
470
471         char *fn;
472         int status;
473
474         argc = rc->values_num + 2;
475         argv = (char **) malloc ((argc + 1) * sizeof (char *));
476         if (argv == NULL)
477                 return (-1);
478
479         fn = strdup (filename);
480         if (fn == NULL)
481         {
482                 free (argv);
483                 return (-1);
484         }
485
486         argv[0] = "update";
487         argv[1] = fn;
488         memcpy (argv + 2, rc->values, rc->values_num * sizeof (char *));
489         argv[argc] = NULL;
490
491         DBG ("rrd_update (argc = %i, argv = %p)", argc, (void *) argv);
492
493         optind = 0; /* bug in librrd? */
494         rrd_clear_error ();
495         status = rrd_update (argc, argv);
496
497         free (argv);
498         free (fn);
499
500         free (rc->values);
501         rc->values = NULL;
502         rc->values_num = 0;
503
504         if (status != 0)
505         {
506                 syslog (LOG_WARNING, "rrd_update failed: %s: %s",
507                                 filename, rrd_get_error ());
508                 return (-1);
509         }
510
511         return (0);
512 } /* int rrd_update_file */
513
514 static void rrd_cache_flush (int timeout)
515 {
516         llentry_t   *le;
517         rrd_cache_t *rc;
518         time_t       now;
519
520         if (cache == NULL)
521                 return;
522
523         DBG ("Flushing cache, timeout = %i", timeout);
524
525         now = time (NULL);
526
527         /* Remove empty entries */
528         le = llist_head (cache);
529         while (le != NULL)
530         {
531                 llentry_t *next = le->next;
532                 rc = (rrd_cache_t *) le->value;
533                 if (rc->values_num == 0)
534                 {
535                         DBG ("Removing cache entry for `%s'", le->key);
536                         free (rc->values);
537                         free (rc);
538                         llist_remove (cache, le);
539                 }
540                 le = next;
541         }
542
543         /* Write timed out entries */
544         le = llist_head (cache);
545         while (le != NULL)
546         {
547                 rc = (rrd_cache_t *) le->value;
548                 if ((now - rc->first_value) >= timeout)
549                         rrd_write_cache_entry (le->key, rc);
550
551                 le = le->next;
552         }
553
554         cache_flush = now;
555 } /* void rrd_cache_flush */
556
557 static int rrd_write (const data_set_t *ds, const value_list_t *vl)
558 {
559         struct stat  statbuf;
560         char         filename[512];
561         char         values[512];
562         rrd_cache_t *rc;
563         time_t       now;
564
565         if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
566                 return (-1);
567
568         if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
569                 return (-1);
570
571         if (stat (filename, &statbuf) == -1)
572         {
573                 if (errno == ENOENT)
574                 {
575                         if (rrd_create_file (filename, ds))
576                                 return (-1);
577                 }
578                 else
579                 {
580                         syslog (LOG_ERR, "stat(%s) failed: %s",
581                                         filename, strerror (errno));
582                         return (-1);
583                 }
584         }
585         else if (!S_ISREG (statbuf.st_mode))
586         {
587                 syslog (LOG_ERR, "stat(%s): Not a regular file!",
588                                 filename);
589                 return (-1);
590         }
591
592         rc = rrd_cache_insert (filename, values);
593         if (rc == NULL)
594                 return (-1);
595
596         if (cache == NULL)
597         {
598                 rrd_write_cache_entry (filename, rc);
599                 free (rc->values);
600                 free (rc);
601                 return (0);
602         }
603
604         now = time (NULL);
605
606         DBG ("age (%s) = %i", filename, now - rc->first_value);
607
608         if ((now - rc->first_value) >= cache_timeout)
609                 rrd_write_cache_entry (filename, rc);
610
611         if ((time (NULL) - cache_flush) >= cache_timeout)
612         {
613                 rrd_cache_flush (cache_timeout);
614         }
615
616         return (0);
617 } /* int rrd_dispatch */
618
619 static int rrd_config (const char *key, const char *val)
620 {
621         if (strcasecmp ("CacheTimeout", key) == 0)
622         {
623                 int tmp = atoi (val);
624                 if (tmp < 0)
625                 {
626                         fprintf (stderr, "rrdtool: `CacheTimeout' must "
627                                         "be greater than 0.\n");
628                         return (1);
629                 }
630                 cache_timeout = tmp;
631         }
632         else if (strcasecmp ("DataDir", key) == 0)
633         {
634                 if (datadir != NULL)
635                         free (datadir);
636                 datadir = strdup (val);
637                 if (datadir != NULL)
638                 {
639                         int len = strlen (datadir);
640                         while ((len > 0) && (datadir[len - 1] == '/'))
641                         {
642                                 len--;
643                                 datadir[len] = '\0';
644                         }
645                         if (len <= 0)
646                         {
647                                 free (datadir);
648                                 datadir = NULL;
649                         }
650                 }
651         }
652         else
653         {
654                 return (-1);
655         }
656         return (0);
657 } /* int rrd_config */
658
659 static int rrd_shutdown (void)
660 {
661         rrd_cache_flush (-1);
662
663         return (0);
664 } /* int rrd_shutdown */
665
666 static int rrd_init (void)
667 {
668         if (cache_timeout < 2)
669         {
670                 cache_timeout = 0;
671         }
672         else
673         {
674                 cache = llist_create ();
675                 cache_flush = time (NULL);
676                 plugin_register_shutdown ("rrdtool", rrd_shutdown);
677         }
678         return (0);
679 } /* int rrd_init */
680
681 void module_register (void)
682 {
683         plugin_register_config ("rrdtool", rrd_config,
684                         config_keys, config_keys_num);
685         plugin_register_init ("rrdtool", rrd_init);
686         plugin_register_write ("rrdtool", rrd_write);
687 }