66e2bf315d1ec6739d9030342d994e572c73757f
[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 #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         "CacheFlush",
80         "DataDir",
81         NULL
82 };
83 static int config_keys_num = 3;
84
85 static char *datadir = NULL;
86
87 static int         cache_timeout = 0;
88 static int         cache_flush_timeout = 0;
89 static time_t      cache_flush_last;
90 static avl_tree_t *cache = NULL;
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 rra_max = rra_timespans_num * rra_types_num;
101
102         int step;
103         int rows;
104         int span;
105
106         int cdp_num;
107         int cdp_len;
108         int i, j;
109
110         char buffer[64];
111
112         if ((rra_num != 0) && (rra_def != NULL))
113         {
114                 *ret = rra_def;
115                 return (rra_num);
116         }
117
118         if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
119                 return (-1);
120         memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
121
122         step = atoi (COLLECTD_STEP);
123         rows = atoi (COLLECTD_ROWS);
124
125         if ((step <= 0) || (rows <= 0))
126         {
127                 *ret = NULL;
128                 return (-1);
129         }
130
131         cdp_len = 0;
132         for (i = 0; i < rra_timespans_num; i++)
133         {
134                 span = rra_timespans[i];
135
136                 if ((span / step) < rows)
137                         continue;
138
139                 if (cdp_len == 0)
140                         cdp_len = 1;
141                 else
142                         cdp_len = (int) floor (((double) span) / ((double) (rows * step)));
143
144                 cdp_num = (int) ceil (((double) span) / ((double) (cdp_len * step)));
145
146                 for (j = 0; j < rra_types_num; j++)
147                 {
148                         if (rra_num >= rra_max)
149                                 break;
150
151                         if (snprintf (buffer, sizeof(buffer), "RRA:%s:%3.1f:%u:%u",
152                                                 rra_types[j], COLLECTD_XFF,
153                                                 cdp_len, cdp_num) >= sizeof (buffer))
154                         {
155                                 syslog (LOG_ERR, "rra_get: Buffer would have been truncated.");
156                                 continue;
157                         }
158
159                         rra_def[rra_num++] = sstrdup (buffer);
160                 }
161         }
162
163 #if COLLECT_DEBUG
164         DBG ("rra_num = %i", rra_num);
165         for (i = 0; i < rra_num; i++)
166                 DBG ("  %s", rra_def[i]);
167 #endif
168
169         *ret = rra_def;
170         return (rra_num);
171 }
172
173 static void ds_free (int ds_num, char **ds_def)
174 {
175         int i;
176
177         for (i = 0; i < ds_num; i++)
178                 if (ds_def[i] != NULL)
179                         free (ds_def[i]);
180         free (ds_def);
181 }
182
183 static int ds_get (char ***ret, const data_set_t *ds)
184 {
185         char **ds_def;
186         int ds_num;
187
188         char min[32];
189         char max[32];
190         char buffer[128];
191
192         DBG ("ds->ds_num = %i", ds->ds_num);
193
194         ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
195         if (ds_def == NULL)
196         {
197                 syslog (LOG_ERR, "rrdtool plugin: malloc failed: %s",
198                                 strerror (errno));
199                 return (-1);
200         }
201         memset (ds_def, '\0', ds->ds_num * sizeof (char *));
202
203         for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
204         {
205                 data_source_t *d = ds->ds + ds_num;
206                 char *type;
207                 int status;
208
209                 ds_def[ds_num] = NULL;
210
211                 if (d->type == DS_TYPE_COUNTER)
212                         type = "COUNTER";
213                 else if (d->type == DS_TYPE_GAUGE)
214                         type = "GAUGE";
215                 else
216                 {
217                         syslog (LOG_ERR, "rrdtool plugin: Unknown DS type: %i",
218                                         d->type);
219                         break;
220                 }
221
222                 if (d->min == NAN)
223                 {
224                         strcpy (min, "U");
225                 }
226                 else
227                 {
228                         snprintf (min, sizeof (min), "%lf", d->min);
229                         min[sizeof (min) - 1] = '\0';
230                 }
231
232                 if (d->max == NAN)
233                 {
234                         strcpy (max, "U");
235                 }
236                 else
237                 {
238                         snprintf (max, sizeof (max), "%lf", d->max);
239                         max[sizeof (max) - 1] = '\0';
240                 }
241
242                 status = snprintf (buffer, sizeof (buffer),
243                                 "DS:%s:%s:%s:%s:%s",
244                                 d->name, type, COLLECTD_HEARTBEAT,
245                                 min, max);
246                 if ((status < 1) || (status >= sizeof (buffer)))
247                         break;
248
249                 ds_def[ds_num] = sstrdup (buffer);
250         } /* for ds_num = 0 .. ds->ds_num */
251
252 #if COLLECT_DEBUG
253 {
254         int i;
255         DBG ("ds_num = %i", ds_num);
256         for (i = 0; i < ds_num; i++)
257                 DBG ("  %s", ds_def[i]);
258 }
259 #endif
260
261         if (ds_num != ds->ds_num)
262         {
263                 ds_free (ds_num, ds_def);
264                 return (-1);
265         }
266
267         *ret = ds_def;
268         return (ds_num);
269 }
270
271 static int rrd_create_file (char *filename, const data_set_t *ds)
272 {
273         char **argv;
274         int argc;
275         char **rra_def;
276         int rra_num;
277         char **ds_def;
278         int ds_num;
279         int i, j;
280         int status = 0;
281
282         if (check_create_dir (filename))
283                 return (-1);
284
285         if ((rra_num = rra_get (&rra_def)) < 1)
286         {
287                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate RRAs");
288                 return (-1);
289         }
290
291         if ((ds_num = ds_get (&ds_def, ds)) < 1)
292         {
293                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate DSes");
294                 return (-1);
295         }
296
297         argc = ds_num + rra_num + 4;
298
299         if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
300         {
301                 syslog (LOG_ERR, "rrd_create failed: %s", strerror (errno));
302                 return (-1);
303         }
304
305         argv[0] = "create";
306         argv[1] = filename;
307         argv[2] = "-s";
308         argv[3] = COLLECTD_STEP;
309
310         j = 4;
311         for (i = 0; i < ds_num; i++)
312                 argv[j++] = ds_def[i];
313         for (i = 0; i < rra_num; i++)
314                 argv[j++] = rra_def[i];
315         argv[j] = NULL;
316
317         optind = 0; /* bug in librrd? */
318         rrd_clear_error ();
319         if (rrd_create (argc, argv) == -1)
320         {
321                 syslog (LOG_ERR, "rrd_create failed: %s: %s", filename, rrd_get_error ());
322                 status = -1;
323         }
324
325         free (argv);
326         ds_free (ds_num, ds_def);
327
328         return (status);
329 }
330
331 static int value_list_to_string (char *buffer, int buffer_len,
332                 const data_set_t *ds, const value_list_t *vl)
333 {
334         int offset;
335         int status;
336         int i;
337
338         memset (buffer, '\0', sizeof (buffer_len));
339
340         status = snprintf (buffer, buffer_len, "%u", (unsigned int) vl->time);
341         if ((status < 1) || (status >= buffer_len))
342                 return (-1);
343         offset = status;
344
345         for (i = 0; i < ds->ds_num; i++)
346         {
347                 if ((ds->ds[i].type != DS_TYPE_COUNTER)
348                                 && (ds->ds[i].type != DS_TYPE_GAUGE))
349                         return (-1);
350
351                 if (ds->ds[i].type == DS_TYPE_COUNTER)
352                         status = snprintf (buffer + offset, buffer_len - offset,
353                                         ":%llu", vl->values[i].counter);
354                 else
355                         status = snprintf (buffer + offset, buffer_len - offset,
356                                         ":%lf", vl->values[i].gauge);
357
358                 if ((status < 1) || (status >= (buffer_len - offset)))
359                         return (-1);
360
361                 offset += status;
362         } /* for ds->ds_num */
363
364         return (0);
365 } /* int value_list_to_string */
366
367 static int value_list_to_filename (char *buffer, int buffer_len,
368                 const data_set_t *ds, const value_list_t *vl)
369 {
370         int offset = 0;
371         int status;
372
373         if (datadir != NULL)
374         {
375                 status = snprintf (buffer + offset, buffer_len - offset,
376                                 "%s/", datadir);
377                 if ((status < 1) || (status >= buffer_len - offset))
378                         return (-1);
379                 offset += status;
380         }
381
382         status = snprintf (buffer + offset, buffer_len - offset,
383                         "%s/", vl->host);
384         if ((status < 1) || (status >= buffer_len - offset))
385                 return (-1);
386         offset += status;
387
388         if (strlen (vl->plugin_instance) > 0)
389                 status = snprintf (buffer + offset, buffer_len - offset,
390                                 "%s-%s/", vl->plugin, vl->plugin_instance);
391         else
392                 status = snprintf (buffer + offset, buffer_len - offset,
393                                 "%s/", vl->plugin);
394         if ((status < 1) || (status >= buffer_len - offset))
395                 return (-1);
396         offset += status;
397
398         if (strlen (vl->type_instance) > 0)
399                 status = snprintf (buffer + offset, buffer_len - offset,
400                                 "%s-%s.rrd", ds->type, vl->type_instance);
401         else
402                 status = snprintf (buffer + offset, buffer_len - offset,
403                                 "%s.rrd", ds->type);
404         if ((status < 1) || (status >= buffer_len - offset))
405                 return (-1);
406         offset += status;
407
408         return (0);
409 } /* int value_list_to_filename */
410
411 static rrd_cache_t *rrd_cache_insert (const char *filename,
412                 const char *value)
413 {
414         rrd_cache_t *rc = NULL;
415
416         if (cache != NULL)
417                 avl_get (cache, filename, (void *) &rc);
418
419         if (rc == NULL)
420         {
421                 rc = (rrd_cache_t *) malloc (sizeof (rrd_cache_t));
422                 if (rc == NULL)
423                         return (NULL);
424                 rc->values_num = 0;
425                 rc->values = NULL;
426                 rc->first_value = 0;
427         }
428
429         rc->values = (char **) realloc ((void *) rc->values,
430                         (rc->values_num + 1) * sizeof (char *));
431         if (rc->values == NULL)
432         {
433                 syslog (LOG_ERR, "rrdtool plugin: realloc failed: %s",
434                                 strerror (errno));
435                 if (cache != NULL)
436                 {
437                         void *cache_key = NULL;
438                         avl_remove (cache, filename, &cache_key, NULL);
439                         sfree (cache_key);
440                 }
441                 free (rc);
442                 return (NULL);
443         }
444
445         rc->values[rc->values_num] = strdup (value);
446         if (rc->values[rc->values_num] != NULL)
447                 rc->values_num++;
448
449         if (rc->values_num == 1)
450                 rc->first_value = time (NULL);
451
452         /* Insert if this is the first value */
453         if ((cache != NULL) && (rc->values_num == 1))
454         {
455                 void *cache_key = strdup (filename);
456
457                 if (cache_key == NULL)
458                 {
459                         syslog (LOG_ERR, "rrdtool plugin: strdup failed: %s",
460                                         strerror (errno));
461                         sfree (rc->values);
462                         sfree (rc);
463                         return (NULL);
464                 }
465
466                 avl_insert (cache, cache_key, rc);
467         }
468
469         DBG ("rrd_cache_insert (%s, %s) = %p", filename, value, (void *) rc);
470
471         return (rc);
472 } /* rrd_cache_t *rrd_cache_insert */
473
474 static int rrd_write_cache_entry (const char *filename, rrd_cache_t *rc)
475 {
476         char **argv;
477         int    argc;
478
479         char *fn;
480         int status;
481
482         int i;
483
484         argc = rc->values_num + 2;
485         argv = (char **) malloc ((argc + 1) * sizeof (char *));
486         if (argv == NULL)
487                 return (-1);
488
489         fn = strdup (filename);
490         if (fn == NULL)
491         {
492                 free (argv);
493                 return (-1);
494         }
495
496         argv[0] = "update";
497         argv[1] = fn;
498         memcpy (argv + 2, rc->values, rc->values_num * sizeof (char *));
499         argv[argc] = NULL;
500
501         DBG ("rrd_update (argc = %i, argv = %p)", argc, (void *) argv);
502
503         optind = 0; /* bug in librrd? */
504         rrd_clear_error ();
505         status = rrd_update (argc, argv);
506
507         free (argv);
508         free (fn);
509
510         for (i = 0; i < rc->values_num; i++)
511                 free (rc->values[i]);
512
513         free (rc->values);
514         rc->values = NULL;
515         rc->values_num = 0;
516
517         if (status != 0)
518         {
519                 syslog (LOG_WARNING, "rrd_update failed: %s: %s",
520                                 filename, rrd_get_error ());
521                 return (-1);
522         }
523
524         return (0);
525 } /* int rrd_write_cache_entry */
526
527 static void rrd_cache_flush (int timeout)
528 {
529         rrd_cache_t *rc;
530         time_t       now;
531
532         char **keys = NULL;
533         int    keys_num = 0;
534
535         char *key;
536         avl_iterator_t *iter;
537         int i;
538
539         if (cache == NULL)
540                 return;
541
542         DBG ("Flushing cache, timeout = %i", timeout);
543
544         now = time (NULL);
545
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], NULL, (void *) &rc) != 0)
573                 {
574                         DBG ("avl_remove (%s) failed.", keys[i]);
575                         continue;
576                 }
577
578                 /* will free `rc' */
579                 rrd_write_cache_entry (keys[i], rc);
580                 sfree (keys[i]); keys[i] = NULL;
581         } /* for (i = 0..keys_num) */
582
583         free (keys);
584         DBG ("Flushed %i value(s)", keys_num);
585
586         cache_flush_last = now;
587 } /* void rrd_cache_flush */
588
589 static int rrd_write (const data_set_t *ds, const value_list_t *vl)
590 {
591         struct stat  statbuf;
592         char         filename[512];
593         char         values[512];
594         rrd_cache_t *rc;
595         time_t       now;
596
597         if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
598                 return (-1);
599
600         if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
601                 return (-1);
602
603         if (stat (filename, &statbuf) == -1)
604         {
605                 if (errno == ENOENT)
606                 {
607                         if (rrd_create_file (filename, ds))
608                                 return (-1);
609                 }
610                 else
611                 {
612                         syslog (LOG_ERR, "stat(%s) failed: %s",
613                                         filename, strerror (errno));
614                         return (-1);
615                 }
616         }
617         else if (!S_ISREG (statbuf.st_mode))
618         {
619                 syslog (LOG_ERR, "stat(%s): Not a regular file!",
620                                 filename);
621                 return (-1);
622         }
623
624         rc = rrd_cache_insert (filename, values);
625         if (rc == NULL)
626                 return (-1);
627
628         if (cache == NULL)
629         {
630                 /* will free `rc' */
631                 rrd_write_cache_entry (filename, rc);
632                 return (0);
633         }
634
635         now = time (NULL);
636
637         DBG ("age (%s) = %i", filename, now - rc->first_value);
638
639         if ((now - rc->first_value) >= cache_timeout)
640                 rrd_write_cache_entry (filename, rc);
641
642         if ((now - cache_flush_last) >= cache_flush_timeout)
643         {
644                 rrd_cache_flush (cache_flush_timeout);
645         }
646
647         return (0);
648 } /* int rrd_write */
649
650 static int rrd_config (const char *key, const char *val)
651 {
652         if (strcasecmp ("CacheTimeout", key) == 0)
653         {
654                 int tmp = atoi (val);
655                 if (tmp < 0)
656                 {
657                         fprintf (stderr, "rrdtool: `CacheTimeout' must "
658                                         "be greater than 0.\n");
659                         return (1);
660                 }
661                 cache_timeout = tmp;
662         }
663         else if (strcasecmp ("CacheFlush", key) == 0)
664         {
665                 int tmp = atoi (val);
666                 if (tmp < 0)
667                 {
668                         fprintf (stderr, "rrdtool: `CacheFlush' must "
669                                         "be greater than 0.\n");
670                         return (1);
671                 }
672                 cache_flush_timeout = tmp;
673         }
674         else if (strcasecmp ("DataDir", key) == 0)
675         {
676                 if (datadir != NULL)
677                         free (datadir);
678                 datadir = strdup (val);
679                 if (datadir != NULL)
680                 {
681                         int len = strlen (datadir);
682                         while ((len > 0) && (datadir[len - 1] == '/'))
683                         {
684                                 len--;
685                                 datadir[len] = '\0';
686                         }
687                         if (len <= 0)
688                         {
689                                 free (datadir);
690                                 datadir = NULL;
691                         }
692                 }
693         }
694         else
695         {
696                 return (-1);
697         }
698         return (0);
699 } /* int rrd_config */
700
701 static int rrd_shutdown (void)
702 {
703         rrd_cache_flush (-1);
704
705         return (0);
706 } /* int rrd_shutdown */
707
708 static int rrd_init (void)
709 {
710         if (cache_timeout < 2)
711         {
712                 cache_timeout = 0;
713                 cache_flush_timeout = 0;
714         }
715         else
716         {
717                 if (cache_flush_timeout < cache_timeout)
718                         cache_flush_timeout = 10 * cache_timeout;
719
720                 cache = avl_create ((int (*) (const void *, const void *)) strcmp);
721                 cache_flush_last = time (NULL);
722                 plugin_register_shutdown ("rrdtool", rrd_shutdown);
723         }
724         return (0);
725 } /* int rrd_init */
726
727 void module_register (void)
728 {
729         plugin_register_config ("rrdtool", rrd_config,
730                         config_keys, config_keys_num);
731         plugin_register_init ("rrdtool", rrd_init);
732         plugin_register_write ("rrdtool", rrd_write);
733 }