#include #include #include #include #include #include #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 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[] = RRDTOOL_CMD; // char cmd[] = "echo"; char *args[512], argstr[ARGSTR_LEN]; int idx = 0, argcnt = 0, i,j; char timestamp[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, sizeof(tmp), updatestr, localtime(&t)); for (i = 0, j = 0; j < sizeof(tmp);) { if (tmp[i] == ':') { timestamp[j++] = '\\'; } timestamp[j++] = tmp[i++]; if (!tmp[i]) break; } timestamp[j] = 0; add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD); add_arg(args, argcnt, argstr, idx, "graph"); add_arg(args, argcnt, argstr, idx, "%s", tmpfile); 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++) { add_arg(args, argcnt, argstr, idx, "%s", image->options[i]); } for (i = 0; image->text[i]; i++) { args[argcnt++] = (char *)image->text[i]; } add_arg(args, argcnt, argstr, idx, "COMMENT: %s\\c", timestamp); run(cmd, args); rename(tmpfile, image->image_filename); return 0; } int rrdtool_draw_images(struct rrd_image **image) { int i; for (i = 0; image[i]; i++) queue_work(WORK_PRIORITY_LOW, "rrdtool_draw_image", (work_fn_t *)rrdtool_draw_image, image[i]); return 0; } 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[RRD_DATA_MAX_LEN + 12]; /* 12 == "%s:" + NULL termination */ char cmd[] = RRDTOOL_CMD; // char cmd[] = "echo"; char *const cmdline[] = { RRDTOOL_CMD, "update", (char *const)rrd->filename, data, 0 }; int l; time_t now = time(NULL); 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'; pr_info("Data: %s\n", 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); /* * 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; }