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