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