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 **/
22 #include "collectd.h"
23 #include "plugin.h"
24 #include "common.h"
25 #include "utils_avltree.h"
26 #include "utils_debug.h"
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
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;
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;
67 static char *rra_types[] =
68 {
69 "AVERAGE",
70 "MIN",
71 "MAX",
72 NULL
73 };
74 static int rra_types_num = 3;
76 static const char *config_keys[] =
77 {
78 "CacheTimeout",
79 "CacheFlush",
80 "DataDir",
81 NULL
82 };
83 static int config_keys_num = 3;
85 static char *datadir = NULL;
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;
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;
100 int rra_max = rra_timespans_num * rra_types_num;
102 int step;
103 int rows;
104 int span;
106 int cdp_num;
107 int cdp_len;
108 int i, j;
110 char buffer[64];
112 if ((rra_num != 0) && (rra_def != NULL))
113 {
114 *ret = rra_def;
115 return (rra_num);
116 }
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 *));
122 step = interval_g;
123 /* FIXME: Use config here */
124 rows = atoi (COLLECTD_ROWS);
126 if ((step <= 0) || (rows <= 0))
127 {
128 *ret = NULL;
129 return (-1);
130 }
132 cdp_len = 0;
133 for (i = 0; i < rra_timespans_num; i++)
134 {
135 span = rra_timespans[i];
137 if ((span / step) < rows)
138 continue;
140 if (cdp_len == 0)
141 cdp_len = 1;
142 else
143 cdp_len = (int) floor (((double) span) / ((double) (rows * step)));
145 cdp_num = (int) ceil (((double) span) / ((double) (cdp_len * step)));
147 for (j = 0; j < rra_types_num; j++)
148 {
149 if (rra_num >= rra_max)
150 break;
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 }
160 rra_def[rra_num++] = sstrdup (buffer);
161 }
162 }
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
170 *ret = rra_def;
171 return (rra_num);
172 }
174 static void ds_free (int ds_num, char **ds_def)
175 {
176 int i;
178 for (i = 0; i < ds_num; i++)
179 if (ds_def[i] != NULL)
180 free (ds_def[i]);
181 free (ds_def);
182 }
184 static int ds_get (char ***ret, const data_set_t *ds)
185 {
186 char **ds_def;
187 int ds_num;
189 char min[32];
190 char max[32];
191 char buffer[128];
193 DBG ("ds->ds_num = %i", ds->ds_num);
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 *));
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;
210 ds_def[ds_num] = NULL;
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 }
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 }
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 }
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;
250 ds_def[ds_num] = sstrdup (buffer);
251 } /* for ds_num = 0 .. ds->ds_num */
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
262 if (ds_num != ds->ds_num)
263 {
264 ds_free (ds_num, ds_def);
265 return (-1);
266 }
268 *ret = ds_def;
269 return (ds_num);
270 }
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;
284 if (check_create_dir (filename))
285 return (-1);
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 }
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 }
299 argc = ds_num + rra_num + 4;
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 }
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 }
314 argv[0] = "create";
315 argv[1] = filename;
316 argv[2] = "-s";
317 argv[3] = step;
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;
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 }
334 free (argv);
335 ds_free (ds_num, ds_def);
337 return (status);
338 }
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;
347 memset (buffer, '\0', sizeof (buffer_len));
349 status = snprintf (buffer, buffer_len, "%u", (unsigned int) vl->time);
350 if ((status < 1) || (status >= buffer_len))
351 return (-1);
352 offset = status;
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);
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);
367 if ((status < 1) || (status >= (buffer_len - offset)))
368 return (-1);
370 offset += status;
371 } /* for ds->ds_num */
373 return (0);
374 } /* int value_list_to_string */
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;
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 }
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;
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;
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;
417 return (0);
418 } /* int value_list_to_filename */
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;
426 if (cache != NULL)
427 avl_get (cache, filename, (void *) &rc);
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 }
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 }
456 rc->values[rc->values_num] = strdup (value);
457 if (rc->values[rc->values_num] != NULL)
458 rc->values_num++;
460 if (rc->values_num == 1)
461 rc->first_value = time (NULL);
463 /* Insert if this is the first value */
464 if ((cache != NULL) && (new_rc == 1))
465 {
466 void *cache_key = strdup (filename);
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 }
478 avl_insert (cache, cache_key, rc);
479 }
481 DBG ("rrd_cache_insert (%s, %s) = %p", filename, value, (void *) rc);
483 return (rc);
484 } /* rrd_cache_t *rrd_cache_insert */
486 static int rrd_write_cache_entry (const char *filename, rrd_cache_t *rc)
487 {
488 char **argv;
489 int argc;
491 char *fn;
492 int status;
494 int i;
496 argc = rc->values_num + 2;
497 argv = (char **) malloc ((argc + 1) * sizeof (char *));
498 if (argv == NULL)
499 return (-1);
501 fn = strdup (filename);
502 if (fn == NULL)
503 {
504 free (argv);
505 return (-1);
506 }
508 argv[0] = "update";
509 argv[1] = fn;
510 memcpy (argv + 2, rc->values, rc->values_num * sizeof (char *));
511 argv[argc] = NULL;
513 DBG ("rrd_update (argc = %i, argv = %p)", argc, (void *) argv);
515 optind = 0; /* bug in librrd? */
516 rrd_clear_error ();
517 status = rrd_update (argc, argv);
519 free (argv);
520 free (fn);
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;
529 if (status != 0)
530 {
531 syslog (LOG_WARNING, "rrd_update failed: %s: %s",
532 filename, rrd_get_error ());
533 return (-1);
534 }
536 return (0);
537 } /* int rrd_write_cache_entry */
539 static void rrd_cache_flush (int timeout)
540 {
541 rrd_cache_t *rc;
542 time_t now;
544 char **keys = NULL;
545 int keys_num = 0;
547 char *key;
548 avl_iterator_t *iter;
549 int i;
551 if (cache == NULL)
552 return;
554 DBG ("Flushing cache, timeout = %i", timeout);
556 now = time (NULL);
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);
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 }
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) */
597 free (keys);
598 DBG ("Flushed %i value(s)", keys_num);
600 cache_flush_last = now;
601 } /* void rrd_cache_flush */
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;
611 if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
612 return (-1);
614 if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
615 return (-1);
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 }
638 rc = rrd_cache_insert (filename, values);
639 if (rc == NULL)
640 return (-1);
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 }
650 now = time (NULL);
652 DBG ("age (%s) = %i", filename, now - rc->first_value);
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);
659 if ((now - cache_flush_last) >= cache_flush_timeout)
660 rrd_cache_flush (cache_flush_timeout);
662 return (0);
663 } /* int rrd_write */
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 */
716 static int rrd_shutdown (void)
717 {
718 rrd_cache_flush (-1);
719 if (cache != NULL)
720 avl_destroy (cache);
721 cache = NULL;
723 return (0);
724 } /* int rrd_shutdown */
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;
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 */
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 }