Merge branch 'collectd-5.5' into collectd-5.6
[collectd.git] / src / utils_rrdcreate.c
1 /**
2  * collectd - src/utils_rrdcreate.c
3  * Copyright (C) 2006-2013  Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "utils_rrdcreate.h"
31
32 #include <pthread.h>
33 #include <rrd.h>
34
35 struct srrd_create_args_s
36 {
37   char *filename;
38   unsigned long pdp_step;
39   time_t last_up;
40   int argc;
41   char **argv;
42 };
43 typedef struct srrd_create_args_s srrd_create_args_t;
44
45 struct async_create_file_s;
46 typedef struct async_create_file_s async_create_file_t;
47 struct async_create_file_s
48 {
49   char *filename;
50   async_create_file_t *next;
51 };
52
53 /*
54  * Private variables
55  */
56 static int rra_timespans[] =
57 {
58   3600,
59   86400,
60   604800,
61   2678400,
62   31622400
63 };
64 static int rra_timespans_num = STATIC_ARRAY_SIZE (rra_timespans);
65
66 static const char *const rra_types[] =
67 {
68   "AVERAGE",
69   "MIN",
70   "MAX"
71 };
72 static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
73
74 #if !defined(HAVE_THREADSAFE_LIBRRD)
75 static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
76 #endif
77
78 static async_create_file_t *async_creation_list = NULL;
79 static pthread_mutex_t async_creation_lock = PTHREAD_MUTEX_INITIALIZER;
80
81 /*
82  * Private functions
83  */
84 static void rra_free (int rra_num, char **rra_def) /* {{{ */
85 {
86   for (int i = 0; i < rra_num; i++)
87   {
88     sfree (rra_def[i]);
89   }
90   sfree (rra_def);
91 } /* }}} void rra_free */
92
93 static void srrd_create_args_destroy (srrd_create_args_t *args)
94 {
95   if (args == NULL)
96     return;
97
98   sfree (args->filename);
99   if (args->argv != NULL)
100   {
101     for (int i = 0; i < args->argc; i++)
102       sfree (args->argv[i]);
103     sfree (args->argv);
104   }
105   sfree (args);
106 } /* void srrd_create_args_destroy */
107
108 static srrd_create_args_t *srrd_create_args_create (const char *filename,
109     unsigned long pdp_step, time_t last_up,
110     int argc, const char **argv)
111 {
112   srrd_create_args_t *args;
113
114   args = calloc (1, sizeof (*args));
115   if (args == NULL)
116   {
117     ERROR ("srrd_create_args_create: calloc failed.");
118     return (NULL);
119   }
120   args->filename = NULL;
121   args->pdp_step = pdp_step;
122   args->last_up = last_up;
123   args->argv = NULL;
124
125   args->filename = strdup (filename);
126   if (args->filename == NULL)
127   {
128     ERROR ("srrd_create_args_create: strdup failed.");
129     srrd_create_args_destroy (args);
130     return (NULL);
131   }
132
133   args->argv = calloc ((size_t) (argc + 1), sizeof (*args->argv));
134   if (args->argv == NULL)
135   {
136     ERROR ("srrd_create_args_create: calloc failed.");
137     srrd_create_args_destroy (args);
138     return (NULL);
139   }
140
141   for (args->argc = 0; args->argc < argc; args->argc++)
142   {
143     args->argv[args->argc] = strdup (argv[args->argc]);
144     if (args->argv[args->argc] == NULL)
145     {
146       ERROR ("srrd_create_args_create: strdup failed.");
147       srrd_create_args_destroy (args);
148       return (NULL);
149     }
150   }
151   assert (args->argc == argc);
152   args->argv[args->argc] = NULL;
153
154   return (args);
155 } /* srrd_create_args_t *srrd_create_args_create */
156
157 /* * * * * * * * * *
158  * WARNING:  Magic *
159  * * * * * * * * * */
160 static int rra_get (char ***ret, const value_list_t *vl, /* {{{ */
161     const rrdcreate_config_t *cfg)
162 {
163   char **rra_def;
164   int rra_num;
165
166   int *rts;
167   int  rts_num;
168
169   int rra_max;
170
171   int cdp_num;
172   int cdp_len;
173
174   /* The stepsize we use here: If it is user-set, use it. If not, use the
175    * interval of the value-list. */
176   int ss;
177
178   if (cfg->rrarows <= 0)
179   {
180     *ret = NULL;
181     return (-1);
182   }
183
184   if ((cfg->xff < 0) || (cfg->xff >= 1.0))
185   {
186     *ret = NULL;
187     return (-1);
188   }
189
190   if (cfg->stepsize > 0)
191     ss = cfg->stepsize;
192   else
193     ss = (int) CDTIME_T_TO_TIME_T (vl->interval);
194   if (ss <= 0)
195   {
196     *ret = NULL;
197     return (-1);
198   }
199
200   /* Use the configured timespans or fall back to the built-in defaults */
201   if (cfg->timespans_num != 0)
202   {
203     rts = cfg->timespans;
204     rts_num = cfg->timespans_num;
205   }
206   else
207   {
208     rts = rra_timespans;
209     rts_num = rra_timespans_num;
210   }
211
212   rra_max = rts_num * rra_types_num;
213   assert (rra_max > 0);
214
215   if ((rra_def = calloc (rra_max + 1, sizeof (*rra_def))) == NULL)
216     return (-1);
217   rra_num = 0;
218
219   cdp_len = 0;
220   for (int i = 0; i < rts_num; i++)
221   {
222     int span = rts[i];
223
224     if ((span / ss) < cfg->rrarows)
225       span = ss * cfg->rrarows;
226
227     if (cdp_len == 0)
228       cdp_len = 1;
229     else
230       cdp_len = (int) floor (((double) span)
231           / ((double) (cfg->rrarows * ss)));
232
233     cdp_num = (int) ceil (((double) span)
234         / ((double) (cdp_len * ss)));
235
236     for (int j = 0; j < rra_types_num; j++)
237     {
238       char buffer[128];
239       int status;
240
241       if (rra_num >= rra_max)
242         break;
243
244       status = ssnprintf (buffer, sizeof (buffer), "RRA:%s:%.10f:%u:%u",
245           rra_types[j], cfg->xff, cdp_len, cdp_num);
246
247       if ((status < 0) || ((size_t) status >= sizeof (buffer)))
248       {
249         ERROR ("rra_get: Buffer would have been truncated.");
250         continue;
251       }
252
253       rra_def[rra_num++] = sstrdup (buffer);
254     }
255   }
256
257   if (rra_num <= 0)
258   {
259     sfree (rra_def);
260     return (0);
261   }
262
263   *ret = rra_def;
264   return (rra_num);
265 } /* }}} int rra_get */
266
267 static void ds_free (int ds_num, char **ds_def) /* {{{ */
268 {
269   for (int i = 0; i < ds_num; i++)
270     if (ds_def[i] != NULL)
271       free (ds_def[i]);
272   free (ds_def);
273 } /* }}} void ds_free */
274
275 static int ds_get (char ***ret, /* {{{ */
276     const data_set_t *ds, const value_list_t *vl,
277     const rrdcreate_config_t *cfg)
278 {
279   char **ds_def;
280   size_t ds_num;
281
282   char min[32];
283   char max[32];
284   char buffer[128];
285
286   assert (ds->ds_num > 0);
287
288   ds_def = calloc (ds->ds_num, sizeof (*ds_def));
289   if (ds_def == NULL)
290   {
291     char errbuf[1024];
292     ERROR ("rrdtool plugin: calloc failed: %s",
293         sstrerror (errno, errbuf, sizeof (errbuf)));
294     return (-1);
295   }
296
297   for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
298   {
299     data_source_t *d = ds->ds + ds_num;
300     const char *type;
301     int status;
302
303     ds_def[ds_num] = NULL;
304
305     if (d->type == DS_TYPE_COUNTER)
306       type = "COUNTER";
307     else if (d->type == DS_TYPE_GAUGE)
308       type = "GAUGE";
309     else if (d->type == DS_TYPE_DERIVE)
310       type = "DERIVE";
311     else if (d->type == DS_TYPE_ABSOLUTE)
312       type = "ABSOLUTE";
313     else
314     {
315       ERROR ("rrdtool plugin: Unknown DS type: %i",
316           d->type);
317       break;
318     }
319
320     if (isnan (d->min))
321     {
322       sstrncpy (min, "U", sizeof (min));
323     }
324     else
325       ssnprintf (min, sizeof (min), "%f", d->min);
326
327     if (isnan (d->max))
328     {
329       sstrncpy (max, "U", sizeof (max));
330     }
331     else
332       ssnprintf (max, sizeof (max), "%f", d->max);
333
334     status = ssnprintf (buffer, sizeof (buffer),
335         "DS:%s:%s:%i:%s:%s",
336         d->name, type,
337         (cfg->heartbeat > 0)
338         ? cfg->heartbeat
339         : (int) CDTIME_T_TO_TIME_T (2 * vl->interval),
340         min, max);
341     if ((status < 1) || ((size_t) status >= sizeof (buffer)))
342       break;
343
344     ds_def[ds_num] = sstrdup (buffer);
345   } /* for ds_num = 0 .. ds->ds_num */
346
347   if (ds_num != ds->ds_num)
348   {
349     ds_free (ds_num, ds_def);
350     return (-1);
351   }
352
353   if (ds_num == 0)
354   {
355     sfree (ds_def);
356     return (0);
357   }
358
359   *ret = ds_def;
360   return (ds_num);
361 } /* }}} int ds_get */
362
363 #if HAVE_THREADSAFE_LIBRRD
364 static int srrd_create (const char *filename, /* {{{ */
365     unsigned long pdp_step, time_t last_up,
366     int argc, const char **argv)
367 {
368   int status;
369   char *filename_copy;
370
371   if ((filename == NULL) || (argv == NULL))
372     return (-EINVAL);
373
374   /* Some versions of librrd don't have the `const' qualifier for the first
375    * argument, so we have to copy the pointer here to avoid warnings. It sucks,
376    * but what else can we do? :(  -octo */
377   filename_copy = strdup (filename);
378   if (filename_copy == NULL)
379   {
380     ERROR ("srrd_create: strdup failed.");
381     return (-ENOMEM);
382   }
383
384   optind = 0; /* bug in librrd? */
385   rrd_clear_error ();
386
387   status = rrd_create_r (filename_copy, pdp_step, last_up,
388       argc, (void *) argv);
389
390   if (status != 0)
391   {
392     WARNING ("rrdtool plugin: rrd_create_r (%s) failed: %s",
393         filename, rrd_get_error ());
394   }
395
396   sfree (filename_copy);
397
398   return (status);
399 } /* }}} int srrd_create */
400 /* #endif HAVE_THREADSAFE_LIBRRD */
401
402 #else /* !HAVE_THREADSAFE_LIBRRD */
403 static int srrd_create (const char *filename, /* {{{ */
404     unsigned long pdp_step, time_t last_up,
405     int argc, const char **argv)
406 {
407   int status;
408
409   int new_argc;
410   char **new_argv;
411
412   char pdp_step_str[16];
413   char last_up_str[16];
414
415   new_argc = 6 + argc;
416   new_argv = malloc ((new_argc + 1) * sizeof (*new_argv));
417   if (new_argv == NULL)
418   {
419     ERROR ("rrdtool plugin: malloc failed.");
420     return (-1);
421   }
422
423   if (last_up == 0)
424     last_up = time (NULL) - 10;
425
426   ssnprintf (pdp_step_str, sizeof (pdp_step_str), "%lu", pdp_step);
427   ssnprintf (last_up_str, sizeof (last_up_str), "%lu", (unsigned long) last_up);
428
429   new_argv[0] = "create";
430   new_argv[1] = (void *) filename;
431   new_argv[2] = "-s";
432   new_argv[3] = pdp_step_str;
433   new_argv[4] = "-b";
434   new_argv[5] = last_up_str;
435
436   memcpy (new_argv + 6, argv, argc * sizeof (char *));
437   new_argv[new_argc] = NULL;
438
439   pthread_mutex_lock (&librrd_lock);
440   optind = 0; /* bug in librrd? */
441   rrd_clear_error ();
442
443   status = rrd_create (new_argc, new_argv);
444   pthread_mutex_unlock (&librrd_lock);
445
446   if (status != 0)
447   {
448     WARNING ("rrdtool plugin: rrd_create (%s) failed: %s",
449         filename, rrd_get_error ());
450   }
451
452   sfree (new_argv);
453
454   return (status);
455 } /* }}} int srrd_create */
456 #endif /* !HAVE_THREADSAFE_LIBRRD */
457
458 static int lock_file (char const *filename) /* {{{ */
459 {
460   async_create_file_t *ptr;
461   struct stat sb;
462   int status;
463
464   pthread_mutex_lock (&async_creation_lock);
465
466   for (ptr = async_creation_list; ptr != NULL; ptr = ptr->next)
467     if (strcmp (filename, ptr->filename) == 0)
468       break;
469
470   if (ptr != NULL)
471   {
472     pthread_mutex_unlock (&async_creation_lock);
473     return (EEXIST);
474   }
475
476   status = stat (filename, &sb);
477   if ((status == 0) || (errno != ENOENT))
478   {
479     pthread_mutex_unlock (&async_creation_lock);
480     return (EEXIST);
481   }
482
483   ptr = malloc (sizeof (*ptr));
484   if (ptr == NULL)
485   {
486     pthread_mutex_unlock (&async_creation_lock);
487     return (ENOMEM);
488   }
489
490   ptr->filename = strdup (filename);
491   if (ptr->filename == NULL)
492   {
493     pthread_mutex_unlock (&async_creation_lock);
494     sfree (ptr);
495     return (ENOMEM);
496   }
497
498   ptr->next = async_creation_list;
499   async_creation_list = ptr;
500
501   pthread_mutex_unlock (&async_creation_lock);
502
503   return (0);
504 } /* }}} int lock_file */
505
506 static int unlock_file (char const *filename) /* {{{ */
507 {
508   async_create_file_t *this;
509   async_create_file_t *prev;
510
511
512   pthread_mutex_lock (&async_creation_lock);
513
514   prev = NULL;
515   for (this = async_creation_list; this != NULL; this = this->next)
516   {
517     if (strcmp (filename, this->filename) == 0)
518       break;
519     prev = this;
520   }
521
522   if (this == NULL)
523   {
524     pthread_mutex_unlock (&async_creation_lock);
525     return (ENOENT);
526   }
527
528   if (prev == NULL)
529   {
530     assert (this == async_creation_list);
531     async_creation_list = this->next;
532   }
533   else
534   {
535     assert (this == prev->next);
536     prev->next = this->next;
537   }
538   this->next = NULL;
539
540   pthread_mutex_unlock (&async_creation_lock);
541
542   sfree (this->filename);
543   sfree (this);
544
545   return (0);
546 } /* }}} int unlock_file */
547
548 static void *srrd_create_thread (void *targs) /* {{{ */
549 {
550   srrd_create_args_t *args = targs;
551   char tmpfile[PATH_MAX];
552   int status;
553
554   status = lock_file (args->filename);
555   if (status != 0)
556   {
557     if (status == EEXIST)
558       NOTICE ("srrd_create_thread: File \"%s\" is already being created.",
559           args->filename);
560     else
561       ERROR ("srrd_create_thread: Unable to lock file \"%s\".",
562           args->filename);
563     srrd_create_args_destroy (args);
564     return (0);
565   }
566
567   ssnprintf (tmpfile, sizeof (tmpfile), "%s.async", args->filename);
568
569   status = srrd_create (tmpfile, args->pdp_step, args->last_up,
570       args->argc, (void *) args->argv);
571   if (status != 0)
572   {
573     WARNING ("srrd_create_thread: srrd_create (%s) returned status %i.",
574         args->filename, status);
575     unlink (tmpfile);
576     unlock_file (args->filename);
577     srrd_create_args_destroy (args);
578     return (0);
579   }
580
581   status = rename (tmpfile, args->filename);
582   if (status != 0)
583   {
584     char errbuf[1024];
585     ERROR ("srrd_create_thread: rename (\"%s\", \"%s\") failed: %s",
586         tmpfile, args->filename,
587         sstrerror (errno, errbuf, sizeof (errbuf)));
588     unlink (tmpfile);
589     unlock_file (args->filename);
590     srrd_create_args_destroy (args);
591     return (0);
592   }
593
594   DEBUG ("srrd_create_thread: Successfully created RRD file \"%s\".",
595       args->filename);
596
597   unlock_file (args->filename);
598   srrd_create_args_destroy (args);
599
600   return (0);
601 } /* }}} void *srrd_create_thread */
602
603 static int srrd_create_async (const char *filename, /* {{{ */
604     unsigned long pdp_step, time_t last_up,
605     int argc, const char **argv)
606 {
607   srrd_create_args_t *args;
608   pthread_t thread;
609   pthread_attr_t attr;
610   int status;
611
612   DEBUG ("srrd_create_async: Creating \"%s\" in the background.", filename);
613
614   args = srrd_create_args_create (filename, pdp_step, last_up, argc, argv);
615   if (args == NULL)
616     return (-1);
617
618   status = pthread_attr_init (&attr);
619   if (status != 0)
620   {
621     srrd_create_args_destroy (args);
622     return (-1);
623   }
624
625   status = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
626   if (status != 0)
627   {
628     pthread_attr_destroy (&attr);
629     srrd_create_args_destroy (args);
630     return (-1);
631   }
632
633   status = pthread_create (&thread, &attr, srrd_create_thread, args);
634   if (status != 0)
635   {
636     char errbuf[1024];
637     ERROR ("srrd_create_async: pthread_create failed: %s",
638         sstrerror (status, errbuf, sizeof (errbuf)));
639     pthread_attr_destroy (&attr);
640     srrd_create_args_destroy (args);
641     return (status);
642   }
643
644   pthread_attr_destroy (&attr);
645   /* args is freed in srrd_create_thread(). */
646   return (0);
647 } /* }}} int srrd_create_async */
648
649 /*
650  * Public functions
651  */
652 int cu_rrd_create_file (const char *filename, /* {{{ */
653     const data_set_t *ds, const value_list_t *vl,
654     const rrdcreate_config_t *cfg)
655 {
656   char **argv;
657   int argc;
658   char **rra_def = NULL;
659   int rra_num;
660   char **ds_def = NULL;
661   int ds_num;
662   int status = 0;
663   time_t last_up;
664   unsigned long stepsize;
665
666   if (check_create_dir (filename))
667     return (-1);
668
669   if ((rra_num = rra_get (&rra_def, vl, cfg)) < 1)
670   {
671     ERROR ("cu_rrd_create_file failed: Could not calculate RRAs");
672     return (-1);
673   }
674
675   if ((ds_num = ds_get (&ds_def, ds, vl, cfg)) < 1)
676   {
677     ERROR ("cu_rrd_create_file failed: Could not calculate DSes");
678     rra_free (rra_num, rra_def);
679     return (-1);
680   }
681
682   argc = ds_num + rra_num;
683
684   if ((argv = malloc (sizeof (*argv) * (argc + 1))) == NULL)
685   {
686     char errbuf[1024];
687     ERROR ("cu_rrd_create_file failed: %s",
688         sstrerror (errno, errbuf, sizeof (errbuf)));
689     rra_free (rra_num, rra_def);
690     ds_free (ds_num, ds_def);
691     return (-1);
692   }
693
694   memcpy (argv, ds_def, ds_num * sizeof (char *));
695   memcpy (argv + ds_num, rra_def, rra_num * sizeof (char *));
696   argv[ds_num + rra_num] = NULL;
697
698   last_up = CDTIME_T_TO_TIME_T (vl->time);
699   if (last_up <= 0)
700     last_up = time (NULL);
701   last_up -= 1;
702
703   if (cfg->stepsize > 0)
704     stepsize = cfg->stepsize;
705   else
706     stepsize = (unsigned long) CDTIME_T_TO_TIME_T (vl->interval);
707
708   if (cfg->async)
709   {
710     status = srrd_create_async (filename, stepsize, last_up,
711         argc, (const char **) argv);
712     if (status != 0)
713       WARNING ("cu_rrd_create_file: srrd_create_async (%s) "
714           "returned status %i.",
715           filename, status);
716   }
717   else /* synchronous */
718   {
719     status = lock_file (filename);
720     if (status != 0)
721     {
722       if (status == EEXIST)
723         NOTICE ("cu_rrd_create_file: File \"%s\" is already being created.",
724             filename);
725       else
726         ERROR ("cu_rrd_create_file: Unable to lock file \"%s\".",
727             filename);
728     }
729     else
730     {
731       status = srrd_create (filename, stepsize, last_up,
732           argc, (const char **) argv);
733
734       if (status != 0)
735       {
736         WARNING ("cu_rrd_create_file: srrd_create (%s) returned status %i.",
737             filename, status);
738       }
739       else
740       {
741         DEBUG ("cu_rrd_create_file: Successfully created RRD file \"%s\".",
742             filename);
743       }
744       unlock_file (filename);
745     }
746   }
747
748   free (argv);
749   ds_free (ds_num, ds_def);
750   rra_free (rra_num, rra_def);
751
752   return (status);
753 } /* }}} int cu_rrd_create_file */
754
755 /* vim: set sw=2 sts=2 et fdm=marker : */