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