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