]> git.itanic.dy.fi Git - rrdd/blob - rrdtool.c
d1e85f4c1d398750ed25d81ad7ee15866f68ad85
[rrdd] / rrdtool.c
1 #include <time.h>
2
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <string.h>
8
9 #include "rrdtool.h"
10 #include "process.h"
11 #include "parser.h"
12 #include "debug.h"
13 #include "string.h"
14 #include "utils.h"
15
16 #define MAX_ARGS        512
17 #define ARGSTR_LEN      32768
18
19 #define RRDTOOL_CMD "/usr/bin/rrdtool"
20
21 /*
22  * Add new argument to a argument list
23  *
24  * args         pointer list to arguments
25  * argcnt       argument counter
26  * argstr       array where the actual arguments are stored
27  * idx          index in the argstr where the new argument will be appended
28  */
29 #define add_arg(args, argcnt, argstr, idx, fmt, arg...) \
30         args[argcnt] = argstr + idx;                    \
31         idx += sprintf(argstr + idx, fmt, ##arg);       \
32         argcnt++;                                       \
33         args[argcnt] = 0;                               \
34         argstr[++idx] = 0
35
36 int rrdtool_draw_image(struct rrd_image *image)
37 {
38         char cmd[] = RRDTOOL_CMD;
39 //      char cmd[] = "echo";
40         char *args[512], argstr[ARGSTR_LEN];
41         int idx = 0, argcnt = 0, i,j;
42         char timestamp[256];
43         char tmp[sizeof(timestamp)];
44         char tmpfile[256];
45         time_t t = time(0);
46         const char *updatestr = "Last update %d.%m.%Y %T (%Z)";
47
48         pr_info("Drawing image %s\n", image->image_filename);
49
50         tmpfile[0] = 0;
51         tmp[0] = 0;
52         strncpy(tmpfile, image->image_filename, sizeof(tmpfile) - 1);
53         strncat(tmpfile, ".tmp",
54                 sizeof(tmpfile) - strlen(image->image_filename) - 1);
55
56         if (image->updatestr)
57                 updatestr = image->updatestr;
58
59         strftime(tmp, sizeof(tmp), updatestr, localtime(&t));
60         for (i = 0, j = 0; j < sizeof(tmp);) {
61                 if (tmp[i] == ':') {
62                         timestamp[j++] = '\\';
63                 }
64                 timestamp[j++] = tmp[i++];
65                 if (!tmp[i])
66                         break;
67         }
68         timestamp[j] = 0;
69
70
71         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
72         add_arg(args, argcnt, argstr, idx, "graph");
73         add_arg(args, argcnt, argstr, idx, "%s", tmpfile);
74
75         add_arg(args, argcnt, argstr, idx, "--start");
76         add_arg(args, argcnt, argstr, idx, "%s", image->timestart);
77         add_arg(args, argcnt, argstr, idx, "--end");
78         add_arg(args, argcnt, argstr, idx, "%s", image->timeend);
79         add_arg(args, argcnt, argstr, idx, "--width");
80         add_arg(args, argcnt, argstr, idx, "%d", image->width);
81         add_arg(args, argcnt, argstr, idx, "--height");
82         add_arg(args, argcnt, argstr, idx, "%d", image->height);
83         add_arg(args, argcnt, argstr, idx, "--imgformat");
84         add_arg(args, argcnt, argstr, idx, "%s", image->imageformat);
85
86         for (i = 0; image->options[i]; i++) {
87                 add_arg(args, argcnt, argstr, idx, "%s", image->options[i]);
88         }
89
90         for (i = 0; image->text[i]; i++) {
91                 args[argcnt++] = (char *)image->text[i];
92         }
93
94         add_arg(args, argcnt, argstr, idx, "COMMENT: %s\\c", timestamp);
95
96         run(cmd, args);
97
98         rename(tmpfile, image->image_filename);
99
100         return 0;
101 }
102
103 int rrdtool_draw_images(struct rrd_image **image)
104 {
105         int i;
106         for (i = 0; image[i]; i++)
107                 queue_work(WORK_PRIORITY_LOW, "rrdtool_draw_image",
108                         (work_fn_t *)rrdtool_draw_image, image[i]);
109
110         return 0;
111 }
112
113 static int sanitize_rrd_update_data(char *data)
114 {
115         char clean_data[RRD_DATA_MAX_LEN];
116         int entries = 0;
117         int minus;
118         char *src, *end, *cln;
119
120         data[RRD_DATA_MAX_LEN - 1] = 0;
121         src = data;
122         cln = clean_data;
123
124         /*
125          * Copy a legit floating point number to clean_data buffer
126          * starting from *src and ending to next ':'. If no legit
127          * number could be found, put a 'U' there instead to make
128          * rrdtool to understand this datapoint is undefined.
129          */
130
131         while (src < data + RRD_DATA_MAX_LEN && *src) {
132                 minus = 0;
133
134                 /* skip any non_numbers but not ':' */
135                 while (*src && !isdigit(*src) && *src != '-' && *src != ':')
136                         src++;
137
138                 if (*src == '-') {
139                         src++;
140                         minus = 1;
141                 }
142
143                 /* Now find the end of the number */
144                 end = skip_numbers(src);
145
146                 /* Floating point numberrs may have a dot with more numbers */
147                 if (*end == '.') {
148                         end++;
149                         end = skip_numbers(end);
150                 }
151
152                 /*
153                  * Now we have gone past the number, there should be a
154                  * colon or zero byte. If src == end, there was no
155                  * number and the entry is undefined instead.
156                  */
157                 if ((*end == ':' || !*end) && src != end) {
158                         if (minus) {
159                                 *cln = '-';
160                                 cln++;
161                         }
162
163                         /*
164                          * Copy the legit number and start copying the
165                          * next one
166                          */
167                         for (; src <= end; src++, cln++)
168                                 *cln = *src;
169
170                         goto next;
171                 }
172
173                 /* Skip over whatever junk there might be */
174                 while (*end != ':' && *end)
175                         end++;
176
177                 /* Mark the entry as undefined */
178                 *cln = 'U';
179                 cln++;
180                 *cln = ':';
181                 cln++;
182         next:
183                 end++;
184                 src = end;
185                 entries++;
186         }
187
188         /*
189          * If last entry was undefined, we need to remove the extra
190          * colon at the end
191          */
192         if (*(cln - 1) == ':')
193                 cln--;
194         *cln = '\0';
195
196         strncpy(data, clean_data, RRD_DATA_MAX_LEN);
197         return entries;
198 }
199
200 static int write_to_logfile(struct rrd_database *rrd, const char *data, time_t now)
201 {
202         int fd, ret;
203         int spacing, i;
204         char filename[1024];
205         char logstr[RRD_DATA_MAX_LEN * 2] = { 0 };
206         const char *time_stamp_fmt = "%Y.%m.%d %H:%M ";
207         char *str_ptr;
208
209         if (!rrd->logfile)
210                 return 0;
211
212         if (rrd->logfile_timestamp_fmt)
213                 time_stamp_fmt = rrd->logfile_timestamp_fmt;
214
215         strftime(filename, sizeof(filename), rrd->logfile, localtime(&now));
216
217         fd = open(filename, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
218         if (fd < 0) {
219                 pr_err("Failed to open file %s for logging: %m\n", filename);
220                 return -1;
221         }
222
223         strftime(logstr, sizeof(logstr), time_stamp_fmt, localtime(&now));
224
225         str_ptr = logstr + strlen(logstr);
226
227         /* Skip the "N: part */
228         while (*data != ':')
229                 data++;
230         data++;
231
232         spacing = 12;
233
234         while (*data && str_ptr - logstr < sizeof(logstr) - 1) {
235                 if (*data == ':') {
236                         *str_ptr++ = ' ';
237                         for (i = 0; i < spacing; i++)
238                                 *str_ptr++ = ' ';
239                         spacing = 12;
240                         data++;
241                         continue;
242                 }
243
244                 *str_ptr++ = *data++;
245                 spacing--;
246         }
247         *str_ptr++ = '\n';
248         *str_ptr++ = 0;
249
250         ret = write(fd, logstr, strlen(logstr));
251         if (ret < 0)
252                 pr_err("Failed to write to logfile %s: %m\n", filename);
253
254         close(fd);
255
256         return ret < 0 ? ret : 0;
257 }
258
259 static int run_post_draw_cmd(struct rrd_database *rrd)
260 {
261         pr_info("Running post draw command for %s\n", rrd->name);
262
263         if (rrd->post_draw_cmd && !strcmp(rrd->post_draw_cmd[0], "shell"))
264                 run(rrd->post_draw_cmd[1], &rrd->post_draw_cmd[1]);
265
266         return 0;
267 }
268
269 static int rrdtool_update_data_multi(struct rrd_database *rrd)
270 {
271         char **data = NULL;
272         int ret, i, d;
273         char cmd[] = RRDTOOL_CMD;
274         char *cmdline[512] = {
275                 RRDTOOL_CMD,
276                 "update",
277                 (char *const)rrd->filename,
278         };
279         int old_last_update = rrd->last_update;
280
281         ret = rrd->parser->parse_multi(&data, rrd->parser_data,
282                                 &rrd->parser_state, rrd->last_update);
283         if (ret < 0) {
284                 pr_err("Parser failure: %d\n", ret);
285                 goto out;
286         }
287
288         for (i = 3, d = 0; i < ARRAY_SIZE(cmdline) - 1; i++, d++) {
289                 time_t then;
290
291                 if (!data[d])
292                         break;
293
294                 sanitize_rrd_update_data(data[d]);
295
296                 then = atoi(data[d]);
297                 write_to_logfile(rrd, data[d], then);
298                 cmdline[i] = data[d];
299                 pr_info("Data: %s\n", data[d]);
300
301                 rrd->last_update = then;
302         }
303
304         cmdline[i] = 0;
305
306         if (ret)
307                 run(cmd, cmdline);
308
309 out:
310         if (data)
311                 for (d = 0; data[d]; d++)
312                         free(data[d]);
313         free(data);
314
315         if (old_last_update == rrd->last_update) {
316                 rrd->update_backoff = time(NULL) + 10;
317                 pr_info("Setting backoff\n");
318         } else
319                 rrd->update_backoff = 0;
320
321         /*
322          * Re-schedule job processing in case we are too far behind
323          * with updates on this database and can start parsing more
324          * data immediately.
325          */
326         notify_job_request();
327
328         return 0;
329 }
330
331 static int do_rrdtool_update_data(struct rrd_database *rrd)
332 {
333         char data[RRD_DATA_MAX_LEN + 12]; /* 12 == "%s:" + NULL termination */
334         char cmd[] = RRDTOOL_CMD;
335 //      char cmd[] = "echo";
336         char *const cmdline[] = {
337                 RRDTOOL_CMD,
338                 "update",
339                 (char *const)rrd->filename,
340                 data,
341                 0
342         };
343         int l;
344         time_t now = time(NULL);
345
346         bzero(data, sizeof(data));
347         l = sprintf(data, "%zd:", now);
348
349         if (rrd->parser && rrd->parser->parse_multi) {
350                 rrdtool_update_data_multi(rrd);
351         } else if (rrd->parser && rrd->parser->parse) {
352                 rrd->parser->parse(data + l, rrd->parser_data,
353                                 &rrd->parser_state);
354                 data[RRD_DATA_MAX_LEN + l] = '\0';
355
356                 pr_info("Data: %s\n", data);
357
358                 sanitize_rrd_update_data(data + l);
359                 write_to_logfile(rrd, data, now);
360
361                 run(cmd, cmdline);
362                 rrd->last_update = now;
363         } else
364                 rrd->last_update = now;
365
366         if (rrd->pre_draw_cmd && !strcmp(rrd->pre_draw_cmd[0], "shell")) {
367                 run(rrd->pre_draw_cmd[1], &rrd->pre_draw_cmd[1]);
368         }
369
370         if (rrd->images)
371                 rrdtool_draw_images(rrd->images);
372
373         /*
374          * We rely on the fact that rrdtool_draw_images queues image
375          * drawings into low priority queue and the post draw queue is
376          * placed on the queue after images. This ensures post draw
377          * command is not started before images are started.
378          *
379          * There is nothing that guarantees post_draw_cmd is executed
380          * after all images are completed though, but it's close..
381          */
382         if (rrd->post_draw_cmd)
383                 queue_work(WORK_PRIORITY_LOW, "rrdtool_post_draw_cmd",
384                         (work_fn_t *)run_post_draw_cmd, rrd);
385
386         rrd->update_active = 0;
387
388         return 0;
389 }
390
391 int rrdtool_update_data(struct rrd_database *rrd)
392 {
393         rrd->update_active = 1;
394
395         return queue_work(WORK_PRIORITY_HIGH, "rrdtool_update_data",
396                         (work_fn_t *)do_rrdtool_update_data, rrd);
397 }
398
399 /*
400  * Walk through the database list and return the first database which
401  * last update is too far in past
402  */
403 struct rrd_database *get_outdated_db(struct rrd_database **dblist)
404 {
405         int i;
406         time_t now = time(0), last;
407
408         for (i = 0; dblist[i]; i++) {
409                 last = max(ROUND_UP(dblist[i]->last_update, dblist[i]->interval),
410                         dblist[i]->update_backoff);
411                 if (!dblist[i]->update_active && last - now <= 0)
412                         return dblist[i];
413         }
414
415         /* Nothing to update this time, return null */
416         return NULL;
417 }
418
419 /*
420  * See how long we may sleep until next update interval window begins
421  */
422 int get_next_update(struct rrd_database **dblist, const char **name)
423 {
424         int i, sleeptime = 0, diff;
425         time_t now = time(0);
426
427         for (i = 0; dblist[i]; i++) {
428                 diff = ROUND_UP(dblist[i]->last_update, dblist[i]->interval) - now;
429                 diff = max(diff, dblist[i]->update_backoff - now);
430
431                 if (dblist[i]->update_active)
432                         diff = (now + dblist[i]->interval) % dblist[i]->interval;
433
434                 if (!sleeptime) {
435                         sleeptime = diff;
436                         *name = dblist[i]->name;
437                 }
438                 if (sleeptime > diff) {
439                         sleeptime = diff;
440                         *name = dblist[i]->name;
441                 }
442         }
443
444         if (sleeptime == 0)
445                 sleeptime = -1;
446
447         return sleeptime;
448 }
449
450 static int database_exists(struct rrd_database *db)
451 {
452         struct stat s;
453
454         /* If the filename exists, stat will return zero */
455         if (db->filename)
456                 return !stat(db->filename, &s);
457
458         return 0;
459 }
460
461 static int get_last_update(struct rrd_database *db)
462 {
463         char cmd[] = RRDTOOL_CMD;
464         char *args[10], argstr[ARGSTR_LEN];
465         char buf[16];
466         int idx = 0, argcnt = 0;
467         int ofd, efd, child;
468         int ret;
469
470         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
471         add_arg(args, argcnt, argstr, idx, "last");
472         add_arg(args, argcnt, argstr, idx, db->filename);
473
474         child = run_piped(cmd, args, NULL, &ofd, &efd);
475         ret = read(ofd, buf, sizeof(buf) - 1);
476         if (ret < 0) {
477                 pr_err("Error reading: %m\n");
478                 buf[0] = 0;
479         } else {
480                 buf[ret] = 0;
481         }
482
483         db->last_update = atoi(buf);
484         pr_info("Last update for %s is: %ld, %ld sec ago\n", db->name, db->last_update,
485                 time(NULL) - db->last_update);
486
487         clear_zombie(child);
488
489         return 0;
490 }
491
492 static int create_database(struct rrd_database *db)
493 {
494         char cmd[] = RRDTOOL_CMD;
495 //      char cmd[] = "echo";
496         char *args[512], argstr[ARGSTR_LEN];
497         int idx = 0, argcnt = 0;
498         int i;
499
500         if (!db->filename) {
501                 pr_err("Database %s missing database filename\n", db->name);
502                 return -1;
503         }
504
505         if (!db->sources || !db->archives) {
506                 pr_err("Cannot create db \"%s\", insufficient source data\n",
507                         db->filename);
508                 return -1;
509         }
510
511         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
512         add_arg(args, argcnt, argstr, idx, "create");
513         add_arg(args, argcnt, argstr, idx, "%s", db->filename);
514         add_arg(args, argcnt, argstr, idx, "--step");
515         add_arg(args, argcnt, argstr, idx, "%d", db->interval);
516
517         for (i = 0; db->sources[i].type; i++) {
518                 add_arg(args, argcnt, argstr, idx, "DS:%s:%s:%d:%f:%f",
519                         db->sources[i].name,
520                         db->sources[i].type,
521                         db->sources[i].heartbeat,
522                         db->sources[i].min,
523                         db->sources[i].max);
524         }
525
526         for (i = 0; db->archives[i].type; i++) {
527                 add_arg(args, argcnt, argstr, idx, "RRA:%s:%f:%d:%d",
528                         db->archives[i].type,
529                         db->archives[i].xff,
530                         db->archives[i].steps,
531                         db->archives[i].rows);
532         }
533
534         run(cmd, args);
535
536         return 0;
537 }
538
539 int rrdtool_create_missing_databases(struct rrd_database *dbs[])
540 {
541         struct rrd_database *db;
542         int i, ret = 0;
543
544         for (i = 0, db = dbs[i]; db; i++, db = dbs[i]) {
545                 if (database_exists(db)) {
546                         pr_info("database %s found\n", db->filename);
547                         get_last_update(db);
548                         continue;
549                 }
550                 pr_info("Database %s missing, creating\n", db->filename);
551                 ret |= create_database(db);
552         }
553
554         return ret;
555 }