]> git.itanic.dy.fi Git - rrdd/blobdiff - rrdtool.c
onewire_parser.c: Fix compiler warnings about string lengths
[rrdd] / rrdtool.c
index 0aff0294364f840f6532705bf15425df91f12b17..f570f5c981d77cbfafac03acdda50eec5c44a206 100644 (file)
--- a/rrdtool.c
+++ b/rrdtool.c
@@ -1,29 +1,62 @@
 #include <time.h>
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
 #include "rrdtool.h"
 #include "process.h"
+#include "parser.h"
+#include "debug.h"
+#include "string.h"
+#include "utils.h"
 
 #define MAX_ARGS       512
 #define ARGSTR_LEN     32768
 
-#define print(fmt, arg...) \
+#define RRDTOOL_CMD "/usr/bin/rrdtool"
+
+/*
+ * Add new argument to a argument list
+ *
+ * args                pointer list to arguments
+ * argcnt      argument counter
+ * argstr      array where the actual arguments are stored
+ * idx         index in the argstr where the new argument will be appended
+ */
+#define add_arg(args, argcnt, argstr, idx, fmt, arg...)        \
        args[argcnt] = argstr + idx;                    \
        idx += sprintf(argstr + idx, fmt, ##arg);       \
        argcnt++;                                       \
+       args[argcnt] = 0;                               \
        argstr[++idx] = 0
 
 int rrdtool_draw_image(struct rrd_image *image)
 {
-       char cmd[] = "/usr/bin/rrdtool";
+       char cmd[] = RRDTOOL_CMD;
 //     char cmd[] = "echo";
        char *args[512], argstr[ARGSTR_LEN];
        int idx = 0, argcnt = 0, i,j;
        char timestamp[256];
-       char tmp[256];
+       char tmp[sizeof(timestamp)];
+       char tmpfile[256];
        time_t t = time(0);
+       const char *updatestr = "Last update %d.%m.%Y %T (%Z)";
+
+       pr_info("Drawing image %s\n", image->image_filename);
+
+       tmpfile[0] = 0;
+       tmp[0] = 0;
+       strncpy(tmpfile, image->image_filename, sizeof(tmpfile) - 1);
+       _strlcat(tmpfile, ".tmp", sizeof(tmpfile));
+
+       if (image->updatestr)
+               updatestr = image->updatestr;
 
-       strftime(tmp, 256, "%d.%m.%Y %T (%Z) ", localtime(&t));
-       for (i = 0, j = 0; j < 256;) {
+       strftime(tmp, sizeof(tmp), updatestr, localtime(&t));
+       for (i = 0, j = 0; j < sizeof(tmp);) {
                if (tmp[i] == ':') {
                        timestamp[j++] = '\\';
                }
@@ -34,35 +67,35 @@ int rrdtool_draw_image(struct rrd_image *image)
        timestamp[j] = 0;
 
 
-       print(" ");
-       print("graph");
-       print("%s", image->image_filename);
+       add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
+       add_arg(args, argcnt, argstr, idx, "graph");
+       add_arg(args, argcnt, argstr, idx, "%s", tmpfile);
 
-       print("--start");
-       print("%s", image->timestart);
-       print("--end");
-       print("%s", image->timeend);
-       print("--width");
-       print("%d", image->width);
-       print("--height");
-       print("%d", image->height);
-       print("--imgformat");
-       print("%s", image->imageformat);
+       add_arg(args, argcnt, argstr, idx, "--start");
+       add_arg(args, argcnt, argstr, idx, "%s", image->timestart);
+       add_arg(args, argcnt, argstr, idx, "--end");
+       add_arg(args, argcnt, argstr, idx, "%s", image->timeend);
+       add_arg(args, argcnt, argstr, idx, "--width");
+       add_arg(args, argcnt, argstr, idx, "%d", image->width);
+       add_arg(args, argcnt, argstr, idx, "--height");
+       add_arg(args, argcnt, argstr, idx, "%d", image->height);
+       add_arg(args, argcnt, argstr, idx, "--imgformat");
+       add_arg(args, argcnt, argstr, idx, "%s", image->imageformat);
 
        for (i = 0; image->options[i]; i++) {
-               print("%s", image->options[i]);
+               add_arg(args, argcnt, argstr, idx, "%s", image->options[i]);
        }
 
        for (i = 0; image->text[i]; i++) {
-               args[argcnt++] = image->text[i];
+               args[argcnt++] = (char *)image->text[i];
        }
 
-       print("COMMENT:Last update %s\\c", timestamp);
-
-       args[argcnt] = 0;
+       add_arg(args, argcnt, argstr, idx, "COMMENT: %s\\c", timestamp);
 
        run(cmd, args);
 
+       rename(tmpfile, image->image_filename);
+
        return 0;
 }
 
@@ -70,39 +103,453 @@ int rrdtool_draw_images(struct rrd_image **image)
 {
        int i;
        for (i = 0; image[i]; i++)
-               rrdtool_draw_image(image[i]);
+               queue_work(WORK_PRIORITY_LOW, "rrdtool_draw_image",
+                       (work_fn_t *)rrdtool_draw_image, image[i]);
 
        return 0;
 }
 
-int rrdtool_update_data(struct rrd_database *rrd)
+static int sanitize_rrd_update_data(char *data)
+{
+       char clean_data[RRD_DATA_MAX_LEN];
+       int entries = 0;
+       int minus;
+       char *src, *end, *cln;
+
+       data[RRD_DATA_MAX_LEN - 1] = 0;
+       src = data;
+       cln = clean_data;
+
+       /*
+        * Copy a legit floating point number to clean_data buffer
+        * starting from *src and ending to next ':'. If no legit
+        * number could be found, put a 'U' there instead to make
+        * rrdtool to understand this datapoint is undefined.
+        */
+
+       while (src < data + RRD_DATA_MAX_LEN && *src) {
+               minus = 0;
+
+               /* skip any non_numbers but not ':' */
+               while (*src && !isdigit(*src) && *src != '-' && *src != ':')
+                       src++;
+
+               if (*src == '-') {
+                       src++;
+                       minus = 1;
+               }
+
+               /* Now find the end of the number */
+               end = skip_numbers(src);
+
+               /* Floating point numberrs may have a dot with more numbers */
+               if (*end == '.') {
+                       end++;
+                       end = skip_numbers(end);
+               }
+
+               /*
+                * Now we have gone past the number, there should be a
+                * colon or zero byte. If src == end, there was no
+                * number and the entry is undefined instead.
+                */
+               if ((*end == ':' || !*end) && src != end) {
+                       if (minus) {
+                               *cln = '-';
+                               cln++;
+                       }
+
+                       /*
+                        * Copy the legit number and start copying the
+                        * next one
+                        */
+                       for (; src <= end; src++, cln++)
+                               *cln = *src;
+
+                       goto next;
+               }
+
+               /* Skip over whatever junk there might be */
+               while (*end != ':' && *end)
+                       end++;
+
+               /* Mark the entry as undefined */
+               *cln = 'U';
+               cln++;
+               *cln = ':';
+               cln++;
+       next:
+               end++;
+               src = end;
+               entries++;
+       }
+
+       /*
+        * If last entry was undefined, we need to remove the extra
+        * colon at the end
+        */
+       if (*(cln - 1) == ':')
+               cln--;
+       *cln = '\0';
+
+       strncpy(data, clean_data, RRD_DATA_MAX_LEN);
+       return entries;
+}
+
+static int write_to_logfile(struct rrd_database *rrd, const char *data, time_t now)
+{
+       int fd, ret;
+       int spacing, i;
+       char filename[1024];
+       char logstr[RRD_DATA_MAX_LEN * 2] = { 0 };
+       const char *time_stamp_fmt = "%Y.%m.%d %H:%M ";
+       char *str_ptr;
+
+       if (!rrd->logfile)
+               return 0;
+
+       if (rrd->logfile_timestamp_fmt)
+               time_stamp_fmt = rrd->logfile_timestamp_fmt;
+
+       strftime(filename, sizeof(filename), rrd->logfile, localtime(&now));
+
+       fd = open(filename, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
+       if (fd < 0) {
+               pr_err("Failed to open file %s for logging: %m\n", filename);
+               return -1;
+       }
+
+       strftime(logstr, sizeof(logstr), time_stamp_fmt, localtime(&now));
+
+       str_ptr = logstr + strlen(logstr);
+
+       /* Skip the "N: part */
+       while (*data != ':')
+               data++;
+       data++;
+
+       spacing = 12;
+
+       while (*data && str_ptr - logstr < sizeof(logstr) - 1) {
+               if (*data == ':') {
+                       *str_ptr++ = ' ';
+                       for (i = 0; i < spacing; i++)
+                               *str_ptr++ = ' ';
+                       spacing = 12;
+                       data++;
+                       continue;
+               }
+
+               *str_ptr++ = *data++;
+               spacing--;
+       }
+       *str_ptr++ = '\n';
+       *str_ptr++ = 0;
+
+       ret = write(fd, logstr, strlen(logstr));
+       if (ret < 0)
+               pr_err("Failed to write to logfile %s: %m\n", filename);
+
+       close(fd);
+
+       return ret < 0 ? ret : 0;
+}
+
+static int run_post_draw_cmd(struct rrd_database *rrd)
+{
+       pr_info("Running post draw command for %s\n", rrd->name);
+
+       if (rrd->post_draw_cmd && !strcmp(rrd->post_draw_cmd[0], "shell"))
+               run(rrd->post_draw_cmd[1], &rrd->post_draw_cmd[1]);
+
+       return 0;
+}
+
+static int rrdtool_update_data_multi(struct rrd_database *rrd)
+{
+       char **data = NULL;
+       int ret, i, d;
+       char cmd[] = RRDTOOL_CMD;
+       char *cmdline[512] = {
+               RRDTOOL_CMD,
+               "update",
+               (char *const)rrd->filename,
+       };
+       int old_last_update = rrd->last_update;
+       time_t now = time(NULL);
+
+       ret = rrd->parser->parse_multi(&data, rrd->parser_data,
+                               &rrd->parser_state, rrd->last_update);
+       if (ret < 0) {
+               pr_err("Parser failure: %d\n", ret);
+               goto out;
+       }
+
+       for (i = 3, d = 0; i < ARRAY_SIZE(cmdline) - 1; d++) {
+               time_t then;
+
+               if (!data[d])
+                       break;
+
+               sanitize_rrd_update_data(data[d]);
+
+               then = atoi(data[d]);
+               if (then > now) {
+                       pr_err("Skipping bad data with timestamp in future: %ld > %ld\n",
+                               then, now);
+                       continue;
+               }
+               write_to_logfile(rrd, data[d], then);
+               cmdline[i] = data[d];
+               pr_info("Data: %s\n", data[d]);
+
+               rrd->last_update = then;
+               i++;
+       }
+
+       cmdline[i] = 0;
+
+       if (ret)
+               run(cmd, cmdline);
+
+out:
+       if (data)
+               for (d = 0; data[d]; d++)
+                       free(data[d]);
+       free(data);
+
+       if (old_last_update == rrd->last_update) {
+               rrd->update_backoff = time(NULL) + 10;
+               pr_info("Setting backoff\n");
+       } else
+               rrd->update_backoff = 0;
+
+       /*
+        * Re-schedule job processing in case we are too far behind
+        * with updates on this database and can start parsing more
+        * data immediately.
+        */
+       notify_job_request();
+
+       return 0;
+}
+
+static int do_rrdtool_update_data(struct rrd_database *rrd)
 {
-       char data[1024];
-       char cmd[] = "/usr/bin/rrdtool";
+       char data[RRD_DATA_MAX_LEN + 12]; /* 12 == "%s:" + NULL termination */
+       char cmd[] = RRDTOOL_CMD;
 //     char cmd[] = "echo";
-       char *cmdline[] = {
-               "",
+       char *const cmdline[] = {
+               RRDTOOL_CMD,
                "update",
-               rrd->filename,
+               (char *const)rrd->filename,
                data,
                0
        };
        int l;
+       time_t now = time(NULL);
 
-       rrd->last_update = time(0);
-       if (do_fork())
-               return 0;
+       bzero(data, sizeof(data));
+       l = sprintf(data, "%zd:", now);
 
-       l = sprintf(data, "N:");
+       if (rrd->parser && rrd->parser->parse_multi) {
+               rrdtool_update_data_multi(rrd);
+       } else if (rrd->parser && rrd->parser->parse) {
+               rrd->parser->parse(data + l, rrd->parser_data,
+                               &rrd->parser_state);
+               data[RRD_DATA_MAX_LEN + l] = '\0';
+
+               pr_info("Data: %s\n", data);
+
+               sanitize_rrd_update_data(data + l);
+               write_to_logfile(rrd, data, now);
 
-       if (rrd->parse) {
-               rrd->parse(data + l);
                run(cmd, cmdline);
+               rrd->last_update = now;
+       } else
+               rrd->last_update = now;
+
+       if (rrd->pre_draw_cmd && !strcmp(rrd->pre_draw_cmd[0], "shell")) {
+               run(rrd->pre_draw_cmd[1], &rrd->pre_draw_cmd[1]);
        }
 
        if (rrd->images)
                rrdtool_draw_images(rrd->images);
 
-       while (harvest_zombies(0));
-       exit(0);
+       /*
+        * We rely on the fact that rrdtool_draw_images queues image
+        * drawings into low priority queue and the post draw queue is
+        * placed on the queue after images. This ensures post draw
+        * command is not started before images are started.
+        *
+        * There is nothing that guarantees post_draw_cmd is executed
+        * after all images are completed though, but it's close..
+        */
+       if (rrd->post_draw_cmd)
+               queue_work(WORK_PRIORITY_LOW, "rrdtool_post_draw_cmd",
+                       (work_fn_t *)run_post_draw_cmd, rrd);
+
+       rrd->update_active = 0;
+
+       return 0;
+}
+
+int rrdtool_update_data(struct rrd_database *rrd)
+{
+       rrd->update_active = 1;
+
+       return queue_work(WORK_PRIORITY_HIGH, "rrdtool_update_data",
+                       (work_fn_t *)do_rrdtool_update_data, rrd);
+}
+
+/*
+ * Walk through the database list and return the first database which
+ * last update is too far in past
+ */
+struct rrd_database *get_outdated_db(struct rrd_database **dblist)
+{
+       int i;
+       time_t now = time(0), last;
+
+       for (i = 0; dblist[i]; i++) {
+               last = max(ROUND_UP(dblist[i]->last_update, dblist[i]->interval),
+                       dblist[i]->update_backoff);
+               if (!dblist[i]->update_active && last - now <= 0)
+                       return dblist[i];
+       }
+
+       /* Nothing to update this time, return null */
+       return NULL;
+}
+
+/*
+ * See how long we may sleep until next update interval window begins
+ */
+int get_next_update(struct rrd_database **dblist, const char **name)
+{
+       int i, sleeptime = -1, diff;
+       time_t now = time(0);
+
+       for (i = 0; dblist[i]; i++) {
+               if (dblist[i]->update_active)
+                       continue;
+
+               diff = ROUND_UP(dblist[i]->last_update, dblist[i]->interval) - now;
+               diff = max(diff, dblist[i]->update_backoff - now);
+
+               if (sleeptime == -1 || sleeptime > diff) {
+                       sleeptime = diff;
+                       *name = dblist[i]->name;
+               }
+       }
+
+       return sleeptime;
+}
+
+static int database_exists(struct rrd_database *db)
+{
+       struct stat s;
+
+       /* If the filename exists, stat will return zero */
+       if (db->filename)
+               return !stat(db->filename, &s);
+
+       return 0;
+}
+
+static int get_last_update(struct rrd_database *db)
+{
+       char cmd[] = RRDTOOL_CMD;
+       char *args[10], argstr[ARGSTR_LEN];
+       char buf[16];
+       int idx = 0, argcnt = 0;
+       int ofd, child;
+       int ret;
+
+       add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
+       add_arg(args, argcnt, argstr, idx, "last");
+       add_arg(args, argcnt, argstr, idx, db->filename);
+
+       child = run_piped(cmd, args, NULL, &ofd, NULL);
+       ret = read(ofd, buf, sizeof(buf) - 1);
+       if (ret < 0) {
+               pr_err("Error reading: %m\n");
+               buf[0] = 0;
+       } else {
+               buf[ret] = 0;
+       }
+
+       db->last_update = atoi(buf);
+       pr_info("Last update for %s is: %ld, %ld sec ago\n", db->name, db->last_update,
+               time(NULL) - db->last_update);
+
+       close(ofd);
+       clear_zombie(child);
+
+       return 0;
+}
+
+static int create_database(struct rrd_database *db)
+{
+       char cmd[] = RRDTOOL_CMD;
+//     char cmd[] = "echo";
+       char *args[512], argstr[ARGSTR_LEN];
+       int idx = 0, argcnt = 0;
+       int i;
+
+       if (!db->filename) {
+               pr_err("Database %s missing database filename\n", db->name);
+               return -1;
+       }
+
+       if (!db->sources || !db->archives) {
+               pr_err("Cannot create db \"%s\", insufficient source data\n",
+                       db->filename);
+               return -1;
+       }
+
+       add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
+       add_arg(args, argcnt, argstr, idx, "create");
+       add_arg(args, argcnt, argstr, idx, "%s", db->filename);
+       add_arg(args, argcnt, argstr, idx, "--step");
+       add_arg(args, argcnt, argstr, idx, "%d", db->interval);
+
+       for (i = 0; db->sources[i].type; i++) {
+               add_arg(args, argcnt, argstr, idx, "DS:%s:%s:%d:%f:%f",
+                       db->sources[i].name,
+                       db->sources[i].type,
+                       db->sources[i].heartbeat,
+                       db->sources[i].min,
+                       db->sources[i].max);
+       }
+
+       for (i = 0; db->archives[i].type; i++) {
+               add_arg(args, argcnt, argstr, idx, "RRA:%s:%f:%d:%d",
+                       db->archives[i].type,
+                       db->archives[i].xff,
+                       db->archives[i].steps,
+                       db->archives[i].rows);
+       }
+
+       run(cmd, args);
+
+       return 0;
+}
+
+int rrdtool_create_missing_databases(struct rrd_database *dbs[])
+{
+       struct rrd_database *db;
+       int i, ret = 0;
+
+       for (i = 0, db = dbs[i]; db; i++, db = dbs[i]) {
+               if (database_exists(db)) {
+                       pr_info("database %s found\n", db->filename);
+                       get_last_update(db);
+                       continue;
+               }
+               pr_info("Database %s missing, creating\n", db->filename);
+               ret |= create_database(db);
+       }
+
+       return ret;
 }