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