#include #include #include #include #include #include #include "rrdtool.h" #include "process.h" #include "parser.h" #include "debug.h" #include "string.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; strncat(tmpfile, image->image_filename, sizeof(tmp) - strlen(tmp) - 1); strncat(tmpfile, ".tmp", sizeof(tmp) - strlen(tmp) - 1); 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 t = time(NULL); 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(&t)); 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(&t)); str_ptr = logstr + strlen(logstr); data += 2; /* Skip the "N: part */ 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 do_rrdtool_update_data(struct rrd_database *rrd) { char data[RRD_DATA_MAX_LEN + 3]; /* 3 == "N:" + NULL termination */ char cmd[] = RRDTOOL_CMD; // char cmd[] = "echo"; char *const cmdline[] = { RRDTOOL_CMD, "update", (char *const)rrd->filename, data, 0 }; int l; bzero(data, sizeof(data)); l = sprintf(data, "N:"); if (rrd->parser && rrd->parser->parse) { rrd->parser->parse(data + l, rrd->parser_data, &rrd->parser_state); data[RRD_DATA_MAX_LEN + 2] = '\0'; pr_info("Data: %s\n", data); sanitize_rrd_update_data(data + l); write_to_logfile(rrd, data); run(cmd, cmdline); } 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); return 0; } int rrdtool_update_data(struct rrd_database *rrd) { rrd->last_update = time(0); 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); for (i = 0; dblist[i]; i++) { if ((dblist[i]->last_update + dblist[i]->interval) - now <= 0) return dblist[i]; } /* Nothing to update this time, return null */ return NULL; } /* * See how long we may sleep until it is required to run an update * again */ int get_next_update(struct rrd_database **dblist, const char **name) { int i, sleeptime = 0, diff; time_t now = time(0); for (i = 0; dblist[i]; i++) { diff = dblist[i]->last_update + dblist[i]->interval - now; if (!sleeptime) { sleeptime = diff; *name = dblist[i]->name; } if (sleeptime > diff) { sleeptime = diff; *name = dblist[i]->name; } if (sleeptime <= 0) return 0; } 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 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); continue; } pr_info("Database %s missing, creating\n", db->filename); ret |= create_database(db); } return ret; }