Merge branch 'collectd-5.7' into collectd-5.8
[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   char *filename;
37   unsigned long pdp_step;
38   time_t last_up;
39   int argc;
40   char **argv;
41 };
42 typedef struct srrd_create_args_s srrd_create_args_t;
43
44 struct async_create_file_s;
45 typedef struct async_create_file_s async_create_file_t;
46 struct async_create_file_s {
47   char *filename;
48   async_create_file_t *next;
49 };
50
51 /*
52  * Private variables
53  */
54 static int rra_timespans[] = {3600, 86400, 604800, 2678400, 31622400};
55 static int rra_timespans_num = STATIC_ARRAY_SIZE(rra_timespans);
56
57 static const char *const rra_types[] = {"AVERAGE", "MIN", "MAX"};
58 static int rra_types_num = STATIC_ARRAY_SIZE(rra_types);
59
60 #if !defined(HAVE_THREADSAFE_LIBRRD)
61 static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
62 #endif
63
64 static async_create_file_t *async_creation_list = NULL;
65 static pthread_mutex_t async_creation_lock = PTHREAD_MUTEX_INITIALIZER;
66
67 /*
68  * Private functions
69  */
70 static void rra_free(int rra_num, char **rra_def) /* {{{ */
71 {
72   for (int i = 0; i < rra_num; i++) {
73     sfree(rra_def[i]);
74   }
75   sfree(rra_def);
76 } /* }}} void rra_free */
77
78 static void srrd_create_args_destroy(srrd_create_args_t *args) {
79   if (args == NULL)
80     return;
81
82   sfree(args->filename);
83   if (args->argv != NULL) {
84     for (int i = 0; i < args->argc; i++)
85       sfree(args->argv[i]);
86     sfree(args->argv);
87   }
88   sfree(args);
89 } /* void srrd_create_args_destroy */
90
91 static srrd_create_args_t *srrd_create_args_create(const char *filename,
92                                                    unsigned long pdp_step,
93                                                    time_t last_up, int argc,
94                                                    const char **argv) {
95   srrd_create_args_t *args;
96
97   args = calloc(1, sizeof(*args));
98   if (args == NULL) {
99     ERROR("srrd_create_args_create: calloc failed.");
100     return NULL;
101   }
102   args->filename = NULL;
103   args->pdp_step = pdp_step;
104   args->last_up = last_up;
105   args->argv = NULL;
106
107   args->filename = strdup(filename);
108   if (args->filename == NULL) {
109     ERROR("srrd_create_args_create: strdup failed.");
110     srrd_create_args_destroy(args);
111     return NULL;
112   }
113
114   args->argv = calloc((size_t)(argc + 1), sizeof(*args->argv));
115   if (args->argv == NULL) {
116     ERROR("srrd_create_args_create: calloc failed.");
117     srrd_create_args_destroy(args);
118     return NULL;
119   }
120
121   for (args->argc = 0; args->argc < argc; args->argc++) {
122     args->argv[args->argc] = strdup(argv[args->argc]);
123     if (args->argv[args->argc] == NULL) {
124       ERROR("srrd_create_args_create: strdup failed.");
125       srrd_create_args_destroy(args);
126       return NULL;
127     }
128   }
129   assert(args->argc == argc);
130   args->argv[args->argc] = NULL;
131
132   return args;
133 } /* srrd_create_args_t *srrd_create_args_create */
134
135 /* * * * * * * * * *
136  * WARNING:  Magic *
137  * * * * * * * * * */
138 static int rra_get(char ***ret, const value_list_t *vl, /* {{{ */
139                    const rrdcreate_config_t *cfg) {
140   char **rra_def;
141   int rra_num;
142
143   int *rts;
144   int rts_num;
145
146   int rra_max;
147
148   int cdp_num;
149   int cdp_len;
150
151   /* The stepsize we use here: If it is user-set, use it. If not, use the
152    * interval of the value-list. */
153   int ss;
154
155   if (cfg->rrarows <= 0) {
156     *ret = NULL;
157     return -1;
158   }
159
160   if ((cfg->xff < 0) || (cfg->xff >= 1.0)) {
161     *ret = NULL;
162     return -1;
163   }
164
165   if (cfg->stepsize > 0)
166     ss = cfg->stepsize;
167   else
168     ss = (int)CDTIME_T_TO_TIME_T(vl->interval);
169   if (ss <= 0) {
170     *ret = NULL;
171     return -1;
172   }
173
174   /* Use the configured timespans or fall back to the built-in defaults */
175   if (cfg->timespans_num != 0) {
176     rts = cfg->timespans;
177     rts_num = cfg->timespans_num;
178   } else {
179     rts = rra_timespans;
180     rts_num = rra_timespans_num;
181   }
182
183   rra_max = rts_num * rra_types_num;
184   assert(rra_max > 0);
185
186   if ((rra_def = calloc(rra_max + 1, sizeof(*rra_def))) == NULL)
187     return -1;
188   rra_num = 0;
189
190   cdp_len = 0;
191   for (int i = 0; i < rts_num; i++) {
192     int span = rts[i];
193
194     if ((span / ss) < cfg->rrarows)
195       span = ss * cfg->rrarows;
196
197     if (cdp_len == 0)
198       cdp_len = 1;
199     else
200       cdp_len = (int)floor(((double)span) / ((double)(cfg->rrarows * ss)));
201
202     cdp_num = (int)ceil(((double)span) / ((double)(cdp_len * ss)));
203
204     for (int j = 0; j < rra_types_num; j++) {
205       char buffer[128];
206       int status;
207
208       if (rra_num >= rra_max)
209         break;
210
211       status = snprintf(buffer, sizeof(buffer), "RRA:%s:%.10f:%u:%u",
212                         rra_types[j], cfg->xff, cdp_len, cdp_num);
213
214       if ((status < 0) || ((size_t)status >= sizeof(buffer))) {
215         ERROR("rra_get: Buffer would have been truncated.");
216         continue;
217       }
218
219       rra_def[rra_num++] = sstrdup(buffer);
220     }
221   }
222
223   if (rra_num <= 0) {
224     sfree(rra_def);
225     return 0;
226   }
227
228   *ret = rra_def;
229   return rra_num;
230 } /* }}} int rra_get */
231
232 static void ds_free(int ds_num, char **ds_def) /* {{{ */
233 {
234   for (int i = 0; i < ds_num; i++)
235     if (ds_def[i] != NULL)
236       free(ds_def[i]);
237   free(ds_def);
238 } /* }}} void ds_free */
239
240 static int ds_get(char ***ret, /* {{{ */
241                   const data_set_t *ds, const value_list_t *vl,
242                   const rrdcreate_config_t *cfg) {
243   char **ds_def;
244   size_t ds_num;
245
246   char min[32];
247   char max[32];
248   char buffer[128];
249
250   assert(ds->ds_num > 0);
251
252   ds_def = calloc(ds->ds_num, sizeof(*ds_def));
253   if (ds_def == NULL) {
254     char errbuf[1024];
255     ERROR("rrdtool plugin: calloc failed: %s",
256           sstrerror(errno, errbuf, sizeof(errbuf)));
257     return -1;
258   }
259
260   for (ds_num = 0; ds_num < ds->ds_num; ds_num++) {
261     data_source_t *d = ds->ds + ds_num;
262     const char *type;
263     int status;
264
265     ds_def[ds_num] = NULL;
266
267     if (d->type == DS_TYPE_COUNTER)
268       type = "COUNTER";
269     else if (d->type == DS_TYPE_GAUGE)
270       type = "GAUGE";
271     else if (d->type == DS_TYPE_DERIVE)
272       type = "DERIVE";
273     else if (d->type == DS_TYPE_ABSOLUTE)
274       type = "ABSOLUTE";
275     else {
276       ERROR("rrdtool plugin: Unknown DS type: %i", d->type);
277       break;
278     }
279
280     if (isnan(d->min)) {
281       sstrncpy(min, "U", sizeof(min));
282     } else
283       snprintf(min, sizeof(min), "%f", d->min);
284
285     if (isnan(d->max)) {
286       sstrncpy(max, "U", sizeof(max));
287     } else
288       snprintf(max, sizeof(max), "%f", d->max);
289
290     status = snprintf(
291         buffer, sizeof(buffer), "DS:%s:%s:%i:%s:%s", d->name, type,
292         (cfg->heartbeat > 0) ? cfg->heartbeat
293                              : (int)CDTIME_T_TO_TIME_T(2 * vl->interval),
294         min, max);
295     if ((status < 1) || ((size_t)status >= sizeof(buffer)))
296       break;
297
298     ds_def[ds_num] = sstrdup(buffer);
299   } /* for ds_num = 0 .. ds->ds_num */
300
301   if (ds_num != ds->ds_num) {
302     ds_free(ds_num, ds_def);
303     return -1;
304   }
305
306   if (ds_num == 0) {
307     sfree(ds_def);
308     return 0;
309   }
310
311   *ret = ds_def;
312   return ds_num;
313 } /* }}} int ds_get */
314
315 #if HAVE_THREADSAFE_LIBRRD
316 static int srrd_create(const char *filename, /* {{{ */
317                        unsigned long pdp_step, time_t last_up, int argc,
318                        const char **argv) {
319   int status;
320   char *filename_copy;
321
322   if ((filename == NULL) || (argv == NULL))
323     return -EINVAL;
324
325   /* Some versions of librrd don't have the `const' qualifier for the first
326    * argument, so we have to copy the pointer here to avoid warnings. It sucks,
327    * but what else can we do? :(  -octo */
328   filename_copy = strdup(filename);
329   if (filename_copy == NULL) {
330     ERROR("srrd_create: strdup failed.");
331     return -ENOMEM;
332   }
333
334   optind = 0; /* bug in librrd? */
335   rrd_clear_error();
336
337   status = rrd_create_r(filename_copy, pdp_step, last_up, argc, (void *)argv);
338
339   if (status != 0) {
340     WARNING("rrdtool plugin: rrd_create_r (%s) failed: %s", filename,
341             rrd_get_error());
342   }
343
344   sfree(filename_copy);
345
346   return status;
347 } /* }}} int srrd_create */
348 /* #endif HAVE_THREADSAFE_LIBRRD */
349
350 #else  /* !HAVE_THREADSAFE_LIBRRD */
351 static int srrd_create(const char *filename, /* {{{ */
352                        unsigned long pdp_step, time_t last_up, int argc,
353                        const char **argv) {
354   int status;
355
356   int new_argc;
357   char **new_argv;
358
359   char pdp_step_str[16];
360   char last_up_str[16];
361
362   new_argc = 6 + argc;
363   new_argv = malloc((new_argc + 1) * sizeof(*new_argv));
364   if (new_argv == NULL) {
365     ERROR("rrdtool plugin: malloc failed.");
366     return -1;
367   }
368
369   if (last_up == 0)
370     last_up = time(NULL) - 10;
371
372   snprintf(pdp_step_str, sizeof(pdp_step_str), "%lu", pdp_step);
373   snprintf(last_up_str, sizeof(last_up_str), "%lu", (unsigned long)last_up);
374
375   new_argv[0] = "create";
376   new_argv[1] = (void *)filename;
377   new_argv[2] = "-s";
378   new_argv[3] = pdp_step_str;
379   new_argv[4] = "-b";
380   new_argv[5] = last_up_str;
381
382   memcpy(new_argv + 6, argv, argc * sizeof(char *));
383   new_argv[new_argc] = NULL;
384
385   pthread_mutex_lock(&librrd_lock);
386   optind = 0; /* bug in librrd? */
387   rrd_clear_error();
388
389   status = rrd_create(new_argc, new_argv);
390   pthread_mutex_unlock(&librrd_lock);
391
392   if (status != 0) {
393     WARNING("rrdtool plugin: rrd_create (%s) failed: %s", filename,
394             rrd_get_error());
395   }
396
397   sfree(new_argv);
398
399   return status;
400 } /* }}} int srrd_create */
401 #endif /* !HAVE_THREADSAFE_LIBRRD */
402
403 static int lock_file(char const *filename) /* {{{ */
404 {
405   async_create_file_t *ptr;
406   struct stat sb;
407   int status;
408
409   pthread_mutex_lock(&async_creation_lock);
410
411   for (ptr = async_creation_list; ptr != NULL; ptr = ptr->next)
412     if (strcmp(filename, ptr->filename) == 0)
413       break;
414
415   if (ptr != NULL) {
416     pthread_mutex_unlock(&async_creation_lock);
417     return EEXIST;
418   }
419
420   status = stat(filename, &sb);
421   if ((status == 0) || (errno != ENOENT)) {
422     pthread_mutex_unlock(&async_creation_lock);
423     return EEXIST;
424   }
425
426   ptr = malloc(sizeof(*ptr));
427   if (ptr == NULL) {
428     pthread_mutex_unlock(&async_creation_lock);
429     return ENOMEM;
430   }
431
432   ptr->filename = strdup(filename);
433   if (ptr->filename == NULL) {
434     pthread_mutex_unlock(&async_creation_lock);
435     sfree(ptr);
436     return ENOMEM;
437   }
438
439   ptr->next = async_creation_list;
440   async_creation_list = ptr;
441
442   pthread_mutex_unlock(&async_creation_lock);
443
444   return 0;
445 } /* }}} int lock_file */
446
447 static int unlock_file(char const *filename) /* {{{ */
448 {
449   async_create_file_t *this;
450   async_create_file_t *prev;
451
452   pthread_mutex_lock(&async_creation_lock);
453
454   prev = NULL;
455   for (this = async_creation_list; this != NULL; this = this->next) {
456     if (strcmp(filename, this->filename) == 0)
457       break;
458     prev = this;
459   }
460
461   if (this == NULL) {
462     pthread_mutex_unlock(&async_creation_lock);
463     return ENOENT;
464   }
465
466   if (prev == NULL) {
467     assert(this == async_creation_list);
468     async_creation_list = this->next;
469   } else {
470     assert(this == prev->next);
471     prev->next = this->next;
472   }
473   this->next = NULL;
474
475   pthread_mutex_unlock(&async_creation_lock);
476
477   sfree(this->filename);
478   sfree(this);
479
480   return 0;
481 } /* }}} int unlock_file */
482
483 static void *srrd_create_thread(void *targs) /* {{{ */
484 {
485   srrd_create_args_t *args = targs;
486   char tmpfile[PATH_MAX];
487   int status;
488
489   status = lock_file(args->filename);
490   if (status != 0) {
491     if (status == EEXIST)
492       NOTICE("srrd_create_thread: File \"%s\" is already being created.",
493              args->filename);
494     else
495       ERROR("srrd_create_thread: Unable to lock file \"%s\".", args->filename);
496     srrd_create_args_destroy(args);
497     return 0;
498   }
499
500   snprintf(tmpfile, sizeof(tmpfile), "%s.async", args->filename);
501
502   status = srrd_create(tmpfile, args->pdp_step, args->last_up, args->argc,
503                        (void *)args->argv);
504   if (status != 0) {
505     WARNING("srrd_create_thread: srrd_create (%s) returned status %i.",
506             args->filename, status);
507     unlink(tmpfile);
508     unlock_file(args->filename);
509     srrd_create_args_destroy(args);
510     return 0;
511   }
512
513   status = rename(tmpfile, args->filename);
514   if (status != 0) {
515     char errbuf[1024];
516     ERROR("srrd_create_thread: rename (\"%s\", \"%s\") failed: %s", tmpfile,
517           args->filename, sstrerror(errno, errbuf, sizeof(errbuf)));
518     unlink(tmpfile);
519     unlock_file(args->filename);
520     srrd_create_args_destroy(args);
521     return 0;
522   }
523
524   DEBUG("srrd_create_thread: Successfully created RRD file \"%s\".",
525         args->filename);
526
527   unlock_file(args->filename);
528   srrd_create_args_destroy(args);
529
530   return 0;
531 } /* }}} void *srrd_create_thread */
532
533 static int srrd_create_async(const char *filename, /* {{{ */
534                              unsigned long pdp_step, time_t last_up, int argc,
535                              const char **argv) {
536   srrd_create_args_t *args;
537   pthread_t thread;
538   pthread_attr_t attr;
539   int status;
540
541   DEBUG("srrd_create_async: Creating \"%s\" in the background.", filename);
542
543   args = srrd_create_args_create(filename, pdp_step, last_up, argc, argv);
544   if (args == NULL)
545     return -1;
546
547   status = pthread_attr_init(&attr);
548   if (status != 0) {
549     srrd_create_args_destroy(args);
550     return -1;
551   }
552
553   status = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
554   if (status != 0) {
555     pthread_attr_destroy(&attr);
556     srrd_create_args_destroy(args);
557     return -1;
558   }
559
560   status = pthread_create(&thread, &attr, srrd_create_thread, args);
561   if (status != 0) {
562     char errbuf[1024];
563     ERROR("srrd_create_async: pthread_create failed: %s",
564           sstrerror(status, errbuf, sizeof(errbuf)));
565     pthread_attr_destroy(&attr);
566     srrd_create_args_destroy(args);
567     return status;
568   }
569
570   pthread_attr_destroy(&attr);
571   /* args is freed in srrd_create_thread(). */
572   return 0;
573 } /* }}} int srrd_create_async */
574
575 /*
576  * Public functions
577  */
578 int cu_rrd_create_file(const char *filename, /* {{{ */
579                        const data_set_t *ds, const value_list_t *vl,
580                        const rrdcreate_config_t *cfg) {
581   char **argv;
582   int argc;
583   char **rra_def = NULL;
584   int rra_num;
585   char **ds_def = NULL;
586   int ds_num;
587   int status = 0;
588   time_t last_up;
589   unsigned long stepsize;
590
591   if (check_create_dir(filename))
592     return -1;
593
594   if ((rra_num = rra_get(&rra_def, vl, cfg)) < 1) {
595     ERROR("cu_rrd_create_file failed: Could not calculate RRAs");
596     return -1;
597   }
598
599   if ((ds_num = ds_get(&ds_def, ds, vl, cfg)) < 1) {
600     ERROR("cu_rrd_create_file failed: Could not calculate DSes");
601     rra_free(rra_num, rra_def);
602     return -1;
603   }
604
605   argc = ds_num + rra_num;
606
607   if ((argv = malloc(sizeof(*argv) * (argc + 1))) == NULL) {
608     char errbuf[1024];
609     ERROR("cu_rrd_create_file failed: %s",
610           sstrerror(errno, errbuf, sizeof(errbuf)));
611     rra_free(rra_num, rra_def);
612     ds_free(ds_num, ds_def);
613     return -1;
614   }
615
616   memcpy(argv, ds_def, ds_num * sizeof(char *));
617   memcpy(argv + ds_num, rra_def, rra_num * sizeof(char *));
618   argv[ds_num + rra_num] = NULL;
619
620   last_up = CDTIME_T_TO_TIME_T(vl->time);
621   if (last_up <= 0)
622     last_up = time(NULL);
623   last_up -= 1;
624
625   if (cfg->stepsize > 0)
626     stepsize = cfg->stepsize;
627   else
628     stepsize = (unsigned long)CDTIME_T_TO_TIME_T(vl->interval);
629
630   if (cfg->async) {
631     status = srrd_create_async(filename, stepsize, last_up, argc,
632                                (const char **)argv);
633     if (status != 0)
634       WARNING("cu_rrd_create_file: srrd_create_async (%s) "
635               "returned status %i.",
636               filename, status);
637   } else /* synchronous */
638   {
639     status = lock_file(filename);
640     if (status != 0) {
641       if (status == EEXIST)
642         NOTICE("cu_rrd_create_file: File \"%s\" is already being created.",
643                filename);
644       else
645         ERROR("cu_rrd_create_file: Unable to lock file \"%s\".", filename);
646     } else {
647       status =
648           srrd_create(filename, stepsize, last_up, argc, (const char **)argv);
649
650       if (status != 0) {
651         WARNING("cu_rrd_create_file: srrd_create (%s) returned status %i.",
652                 filename, status);
653       } else {
654         DEBUG("cu_rrd_create_file: Successfully created RRD file \"%s\".",
655               filename);
656       }
657       unlock_file(filename);
658     }
659   }
660
661   free(argv);
662   ds_free(ds_num, ds_def);
663   rra_free(rra_num, rra_def);
664
665   return status;
666 } /* }}} int cu_rrd_create_file */