]> git.itanic.dy.fi Git - rrdd/blob - rrdtool.c
Allow parsers to store private data to databases
[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) - 1);
51         strncat(tmpfile, ".tmp", sizeof(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         data += 2;      /* Skip the "N: part */
226         spacing = 12;
227
228         while (*data && str_ptr - logstr < sizeof(logstr) - 1) {
229                 if (*data == ':') {
230                         *str_ptr++ = ' ';
231                         for (i = 0; i < spacing; i++)
232                                 *str_ptr++ = ' ';
233                         spacing = 12;
234                         data++;
235                         continue;
236                 }
237
238                 *str_ptr++ = *data++;
239                 spacing--;
240         }
241         *str_ptr++ = '\n';
242         *str_ptr++ = 0;
243
244         ret = write(fd, logstr, strlen(logstr));
245         if (ret < 0)
246                 pr_err("Failed to write to logfile %s: %m\n", filename);
247
248         close(fd);
249
250         return ret < 0 ? ret : 0;
251 }
252
253 static int run_post_draw_cmd(struct rrd_database *rrd)
254 {
255         pr_info("Running post draw command for %s\n", rrd->name);
256
257         if (rrd->post_draw_cmd && !strcmp(rrd->post_draw_cmd[0], "shell"))
258                 run(rrd->post_draw_cmd[1], &rrd->post_draw_cmd[1]);
259
260         return 0;
261 }
262
263 static int do_rrdtool_update_data(struct rrd_database *rrd)
264 {
265         char data[RRD_DATA_MAX_LEN + 3]; /* 3 == "N:" + NULL termination */
266         char cmd[] = RRDTOOL_CMD;
267 //      char cmd[] = "echo";
268         char *const cmdline[] = {
269                 RRDTOOL_CMD,
270                 "update",
271                 (char *const)rrd->filename,
272                 data,
273                 0
274         };
275         int l;
276
277         bzero(data, sizeof(data));
278         l = sprintf(data, "N:");
279
280         if (rrd->parser && rrd->parser->parse) {
281                 rrd->parser->parse(data + l, rrd->parser_data,
282                                 &rrd->parser_state);
283                 data[RRD_DATA_MAX_LEN + 2] = '\0';
284
285                 pr_info("Data: %s\n", data);
286
287                 sanitize_rrd_update_data(data + l);
288                 write_to_logfile(rrd, data);
289
290                 run(cmd, cmdline);
291         }
292
293         if (rrd->pre_draw_cmd && !strcmp(rrd->pre_draw_cmd[0], "shell")) {
294                 run(rrd->pre_draw_cmd[1], &rrd->pre_draw_cmd[1]);
295         }
296
297         if (rrd->images)
298                 rrdtool_draw_images(rrd->images);
299
300         /*
301          * We rely on the fact that rrdtool_draw_images queues image
302          * drawings into low priority queue and the post draw queue is
303          * placed on the queue after images. This ensures post draw
304          * command is not started before images are started.
305          *
306          * There is nothing that guarantees post_draw_cmd is executed
307          * after all images are completed though, but it's close..
308          */
309         if (rrd->post_draw_cmd)
310                 queue_work(WORK_PRIORITY_LOW, "rrdtool_post_draw_cmd",
311                         (work_fn_t *)run_post_draw_cmd, rrd);
312
313         return 0;
314 }
315
316 int rrdtool_update_data(struct rrd_database *rrd)
317 {
318         rrd->last_update = time(0);
319
320         return queue_work(WORK_PRIORITY_HIGH, "rrdtool_update_data",
321                         (work_fn_t *)do_rrdtool_update_data, rrd);
322 }
323
324 /*
325  * Walk through the database list and return the first database which
326  * last update is too far in past
327  */
328 struct rrd_database *get_outdated_db(struct rrd_database **dblist)
329 {
330         int i;
331         time_t now = time(0);
332
333         for (i = 0; dblist[i]; i++) {
334                 if ((dblist[i]->last_update + dblist[i]->interval) - now <= 0)
335                         return dblist[i];
336         }
337
338         /* Nothing to update this time, return null */
339         return NULL;
340 }
341
342 /*
343  * See how long we may sleep until it is required to run an update
344  * again
345  */
346 int get_next_update(struct rrd_database **dblist, const char **name)
347 {
348         int i, sleeptime = 0, diff;
349         time_t now = time(0);
350
351         for (i = 0; dblist[i]; i++) {
352                 diff = dblist[i]->last_update + dblist[i]->interval - now;
353                 if (!sleeptime) {
354                         sleeptime = diff;
355                         *name = dblist[i]->name;
356                 }
357                 if (sleeptime > diff) {
358                         sleeptime = diff;
359                         *name = dblist[i]->name;
360                 }
361                 if (sleeptime <= 0)
362                         return 0;
363         }
364
365         return sleeptime;
366 }
367
368 static int database_exists(struct rrd_database *db)
369 {
370         struct stat s;
371
372         /* If the filename exists, stat will return zero */
373         if (db->filename)
374                 return !stat(db->filename, &s);
375
376         return 0;
377 }
378
379 static int create_database(struct rrd_database *db)
380 {
381         char cmd[] = RRDTOOL_CMD;
382 //      char cmd[] = "echo";
383         char *args[512], argstr[ARGSTR_LEN];
384         int idx = 0, argcnt = 0;
385         int i;
386
387         if (!db->filename) {
388                 pr_err("Database %s missing database filename\n", db->name);
389                 return -1;
390         }
391
392         if (!db->sources || !db->archives) {
393                 pr_err("Cannot create db \"%s\", insufficient source data\n",
394                         db->filename);
395                 return -1;
396         }
397
398         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
399         add_arg(args, argcnt, argstr, idx, "create");
400         add_arg(args, argcnt, argstr, idx, "%s", db->filename);
401         add_arg(args, argcnt, argstr, idx, "--step");
402         add_arg(args, argcnt, argstr, idx, "%d", db->interval);
403
404         for (i = 0; db->sources[i].type; i++) {
405                 add_arg(args, argcnt, argstr, idx, "DS:%s:%s:%d:%f:%f",
406                         db->sources[i].name,
407                         db->sources[i].type,
408                         db->sources[i].heartbeat,
409                         db->sources[i].min,
410                         db->sources[i].max);
411         }
412
413         for (i = 0; db->archives[i].type; i++) {
414                 add_arg(args, argcnt, argstr, idx, "RRA:%s:%f:%d:%d",
415                         db->archives[i].type,
416                         db->archives[i].xff,
417                         db->archives[i].steps,
418                         db->archives[i].rows);
419         }
420
421         run(cmd, args);
422
423         return 0;
424 }
425
426 int rrdtool_create_missing_databases(struct rrd_database *dbs[])
427 {
428         struct rrd_database *db;
429         int i, ret = 0;
430
431         for (i = 0, db = dbs[i]; db; i++, db = dbs[i]) {
432                 if (database_exists(db)) {
433                         pr_info("database %s found\n", db->filename);
434                         continue;
435                 }
436                 pr_info("Database %s missing, creating\n", db->filename);
437                 ret |= create_database(db);
438         }
439
440         return ret;
441 }