Merged `branches/log-mode' to `trunk'
[collectd.git] / src / common.c
index 0a4cba3..a4b718c 100644 (file)
 #include "common.h"
 #include "utils_debug.h"
 
+#ifdef HAVE_MATH_H
+#  include <math.h>
+#endif
+
+extern int operating_mode;
+
 #ifdef HAVE_LIBKSTAT
 extern kstat_ctl_t *kc;
 #endif
 
 #ifdef HAVE_LIBRRD
+#if 0
 static char *rra_def[] =
 {
                "RRA:AVERAGE:0.0:1:1500",
@@ -47,6 +54,27 @@ static char *rra_def[] =
                NULL
 };
 static int rra_num = 12;
+#endif
+
+static int rra_timespans[] =
+{
+       3600,
+       86400,
+       604800,
+       2678400,
+       31622400,
+       0
+};
+static int rra_timespans_num = 5;
+
+static char *rra_types[] =
+{
+       "AVERAGE",
+       "MIN",
+       "MAX",
+       NULL
+};
+static int rra_types_num = 3;
 #endif /* HAVE_LIBRRD */
 
 void sstrncpy (char *d, const char *s, int len)
@@ -156,6 +184,27 @@ int strjoin (char *dst, size_t dst_len,
        return (strlen (dst));
 }
 
+int strsubstitute (char *str, char c_from, char c_to)
+{
+       int ret;
+
+       if (str == NULL)
+               return (-1);
+
+       ret = 0;
+       while (*str != '\0')
+       {
+               if (*str == c_from)
+               {
+                       *str = c_to;
+                       ret++;
+               }
+               str++;
+       }
+
+       return (ret);
+}
+
 int escape_slashes (char *buf, int buf_len)
 {
        int i;
@@ -184,8 +233,30 @@ int escape_slashes (char *buf, int buf_len)
        return (0);
 }
 
-#ifdef HAVE_LIBRRD
-int check_create_dir (const char *file_orig)
+int timeval_sub_timespec (struct timeval *tv0, struct timeval *tv1, struct timespec *ret)
+{
+       if ((tv0 == NULL) || (tv1 == NULL) || (ret == NULL))
+               return (-2);
+
+       if ((tv0->tv_sec < tv1->tv_sec)
+                       || ((tv0->tv_sec == tv1->tv_sec) && (tv0->tv_usec < tv1->tv_usec)))
+               return (-1);
+
+       ret->tv_sec  = tv0->tv_sec - tv1->tv_sec;
+       ret->tv_nsec = 1000 * ((long) (tv0->tv_usec - tv1->tv_usec));
+
+       if (ret->tv_nsec < 0)
+       {
+               assert (ret->tv_sec > 0);
+
+               ret->tv_nsec += 1000000000;
+               ret->tv_sec  -= 1;
+       }
+
+       return (0);
+}
+
+static int check_create_dir (const char *file_orig)
 {
        struct stat statbuf;
 
@@ -289,16 +360,236 @@ int check_create_dir (const char *file_orig)
        return (0);
 }
 
-int rrd_create_file (char *filename, char **ds_def, int ds_num)
+/* * * * *
+ * Magic *
+ * * * * */
+int rra_get (char ***ret)
+{
+       static char **rra_def = NULL;
+       static int rra_num = 0;
+
+       int rra_max = rra_timespans_num * rra_types_num;
+
+       int step;
+       int rows;
+       int span;
+
+       int cdp_num;
+       int cdp_len;
+       int i, j;
+
+       char buffer[64];
+
+       if ((rra_num != 0) && (rra_def != NULL))
+       {
+               *ret = rra_def;
+               return (rra_num);
+       }
+
+       if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
+               return (-1);
+       memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
+
+       step = atoi (COLLECTD_STEP);
+       rows = atoi (COLLECTD_ROWS);
+
+       if ((step <= 0) || (rows <= 0))
+       {
+               *ret = NULL;
+               return (-1);
+       }
+
+       cdp_len = 0;
+       for (i = 0; i < rra_timespans_num; i++)
+       {
+               span = rra_timespans[i];
+
+               if ((span / step) < rows)
+                       continue;
+
+               if (cdp_len == 0)
+                       cdp_len = 1;
+               else
+                       cdp_len = (int) floor (((double) span) / ((double) (rows * step)));
+
+               cdp_num = (int) ceil (((double) span) / ((double) (cdp_len * step)));
+
+               for (j = 0; j < rra_types_num; j++)
+               {
+                       if (rra_num >= rra_max)
+                               break;
+
+                       if (snprintf (buffer, sizeof(buffer), "RRA:%s:%3.1f:%u:%u",
+                                               rra_types[j], COLLECTD_XFF,
+                                               cdp_len, cdp_num) >= sizeof (buffer))
+                       {
+                               syslog (LOG_ERR, "rra_get: Buffer would have been truncated.");
+                               continue;
+                       }
+
+                       rra_def[rra_num++] = sstrdup (buffer);
+               }
+       }
+
+#if COLLECT_DEBUG
+       DBG ("rra_num = %i", rra_num);
+       for (i = 0; i < rra_num; i++)
+               DBG ("  %s", rra_def[i]);
+#endif
+
+       *ret = rra_def;
+       return (rra_num);
+}
+
+static int log_create_file (char *filename, char **ds_def, int ds_num)
+{
+       FILE *log;
+       int i;
+
+       log = fopen (filename, "w");
+       if (log == NULL)
+       {
+               syslog (LOG_WARNING, "Failed to create %s: %s", filename,
+                               strerror(errno));
+               return (-1);
+       }
+
+       fprintf (log, "epoch");
+       for (i = 0; i < ds_num; i++)
+       {
+               char *name;
+               char *tmp;
+
+               name = strchr (ds_def[i], ':');
+               if (name == NULL)
+               {
+                       syslog (LOG_WARNING, "Invalid DS definition '%s' for %s",
+                                       ds_def[i], filename);
+                       fclose(log);
+                       remove(filename);
+                       return (-1);
+               }
+
+               name += 1;
+               tmp = strchr (name, ':');
+               if (tmp == NULL)
+               {
+                       syslog (LOG_WARNING, "Invalid DS definition '%s' for %s",
+                                       ds_def[i], filename);
+                       fclose(log);
+                       remove(filename);
+                       return (-1);
+               }
+
+               /* The `%.*s' is needed because there is no null-byte behind
+                * the name. */
+               fprintf(log, ",%.*s", (tmp - name), name);
+       }
+       fprintf(log, "\n");
+       fclose(log);
+
+       return 0;
+}
+
+static int log_update_file (char *host, char *file, char *values,
+               char **ds_def, int ds_num)
+{
+       char *tmp;
+       FILE *fp;
+       struct stat statbuf;
+       char full_file[1024];
+
+       /* Cook the values a bit: Substitute colons with commas */
+       strsubstitute (values, ':', ',');
+
+       /* host == NULL => local mode */
+       if (host != NULL)
+       {
+               if (snprintf (full_file, 1024, "%s/%s", host, file) >= 1024)
+                       return (-1);
+       }
+       else
+       {
+               if (snprintf (full_file, 1024, "%s", file) >= 1024)
+                       return (-1);
+       }
+
+       strncpy (full_file, file, 1024);
+
+       tmp = full_file + strlen (full_file) - 4;
+       assert (tmp > 0);
+
+       /* Change the filename for logfiles. */
+       if (strncmp (tmp, ".rrd", 4) == 0)
+       {
+               time_t now;
+               struct tm *tm;
+
+               /* TODO: Find a way to minimize the calls to `localtime', since
+                * they are pretty expensive.. */
+               now = time (NULL);
+               tm = localtime (&now);
+
+               strftime (tmp, 1024 - (tmp - full_file), "-%Y-%m-%d", tm);
+
+               /* `localtime(3)' returns a pointer to static data,
+                * therefore the pointer may not be free'd. */
+       }
+       else
+               DBG ("The filename ends with `%s' which is unexpected.", tmp);
+
+       if (stat (full_file, &statbuf) == -1)
+       {
+               if (errno == ENOENT)
+               {
+                       if (log_create_file (full_file, ds_def, ds_num))
+                               return (-1);
+               }
+               else
+               {
+                       syslog (LOG_ERR, "stat %s: %s", full_file, strerror (errno));
+                       return (-1);
+               }
+       }
+       else if (!S_ISREG (statbuf.st_mode))
+       {
+               syslog (LOG_ERR, "stat %s: Not a regular file!", full_file);
+               return (-1);
+       }
+
+
+       fp = fopen (full_file, "a");
+       if (fp == NULL)
+       {
+               syslog (LOG_WARNING, "Failed to append to %s: %s", full_file,
+                               strerror(errno));
+               return (-1);
+       }
+       fprintf(fp, "%s\n", values);
+       fclose(fp);
+
+       return (0);
+} /* int log_update_file */
+
+#if HAVE_LIBRRD
+static int rrd_create_file (char *filename, char **ds_def, int ds_num)
 {
        char **argv;
        int argc;
+       char **rra_def;
+       int rra_num;
        int i, j;
        int status = 0;
 
        if (check_create_dir (filename))
                return (-1);
 
+       if ((rra_num = rra_get (&rra_def)) < 1)
+       {
+               syslog (LOG_ERR, "rra_create failed: Could not calculate RRAs");
+               return (-1);
+       }
+
        argc = ds_num + rra_num + 4;
 
        if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
@@ -328,7 +619,7 @@ int rrd_create_file (char *filename, char **ds_def, int ds_num)
        }
 
        free (argv);
-       
+
        return (status);
 }
 #endif /* HAVE_LIBRRD */
@@ -336,11 +627,19 @@ int rrd_create_file (char *filename, char **ds_def, int ds_num)
 int rrd_update_file (char *host, char *file, char *values,
                char **ds_def, int ds_num)
 {
-#ifdef HAVE_LIBRRD
+#if HAVE_LIBRRD
        struct stat statbuf;
        char full_file[1024];
        char *argv[4] = { "update", full_file, values, NULL };
+#endif /* HAVE_LIBRRD */
 
+       /* I'd rather have a function `common_update_file' to make this
+        * decission, but for that we'd need to touch all plugins.. */
+       if (operating_mode == MODE_LOG)
+               return (log_update_file (host, file, values,
+                                       ds_def, ds_num));
+
+#if HAVE_LIBRRD
        /* host == NULL => local mode */
        if (host != NULL)
        {
@@ -379,9 +678,13 @@ int rrd_update_file (char *host, char *file, char *values,
                syslog (LOG_WARNING, "rrd_update failed: %s: %s", full_file, rrd_get_error ());
                return (-1);
        }
-#endif /* HAVE_LIBRRD */
-
        return (0);
+/* #endif HAVE_LIBRRD */
+
+#else
+       syslog (LOG_ERR, "`rrd_update_file' was called, but collectd isn't linked against librrd!");
+       return (-1);
+#endif
 }
 
 #ifdef HAVE_LIBKSTAT