#include <sys/types.h>
#include <sys/stat.h>
+#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "parser.h"
#include "debug.h"
#include "string.h"
+#include "utils.h"
#define MAX_ARGS 512
#define ARGSTR_LEN 32768
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 *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);
- strftime(tmp, 256, "%d.%m.%Y %T (%Z) ", localtime(&t));
- for (i = 0, j = 0; j < 256;) {
+ 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, sizeof(tmp), updatestr, localtime(&t));
+ for (i = 0, j = 0; j < sizeof(tmp);) {
if (tmp[i] == ':') {
timestamp[j++] = '\\';
}
timestamp[j] = 0;
- add_arg(args, argcnt, argstr, idx, " ");
+ add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
add_arg(args, argcnt, argstr, idx, "graph");
- add_arg(args, argcnt, argstr, idx, "%s", image->image_filename);
+ add_arg(args, argcnt, argstr, idx, "%s", tmpfile);
add_arg(args, argcnt, argstr, idx, "--start");
add_arg(args, argcnt, argstr, idx, "%s", image->timestart);
}
for (i = 0; image->text[i]; i++) {
- args[argcnt++] = image->text[i];
+ args[argcnt++] = (char *)image->text[i];
}
- add_arg(args, argcnt, argstr, idx, "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;
}
{
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;
}
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);
}
- if (*end == ':' || !*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 = ':';
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;
}
-int rrdtool_update_data(struct rrd_database *rrd)
+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[RRD_DATA_MAX_LEN + 2];
+ 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);
+
+ 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';
- l = sprintf(data, "N:");
+ pr_info("Data: %s\n", data);
- if (rrd->parse) {
- rrd->parse(data + l, rrd->parser_data);
sanitize_rrd_update_data(data + l);
+ write_to_logfile(rrd, data, now);
+
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)
if (db->filename)
return !stat(db->filename, &s);
- return 1;
+ 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[] = "echo";
char *args[512], argstr[ARGSTR_LEN];
int idx = 0, argcnt = 0;
- int child, i;
+ int i;
+
+ if (!db->filename) {
+ pr_err("Database %s missing database filename\n", db->name);
+ return -1;
+ }
if (!db->sources || !db->archives) {
- printf("Cannot create db \"%s\", insufficient source data\n",
+ pr_err("Cannot create db \"%s\", insufficient source data\n",
db->filename);
return -1;
}
- add_arg(args, argcnt, argstr, idx, " ");
+ 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");
db->archives[i].rows);
}
- child = run(cmd, args);
-
- harvest_zombies(child);
+ run(cmd, args);
return 0;
}
-int rrdtool_check_databases(struct rrd_database *dbs[])
+int rrdtool_create_missing_databases(struct rrd_database *dbs[])
{
struct rrd_database *db;
- int i;
+ int i, ret = 0;
for (i = 0, db = dbs[i]; db; i++, db = dbs[i]) {
if (database_exists(db)) {
- printf("database %s found\n", db->filename);
+ pr_info("database %s found\n", db->filename);
+ get_last_update(db);
continue;
}
- printf("Database %s missing, creating\n", db->filename);
- create_database(db);
+ pr_info("Database %s missing, creating\n", db->filename);
+ ret |= create_database(db);
}
- printf("All done\n");
- return 0;
+ return ret;
}