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