rrdtool plugin: First implementation of an rrdtool write plugin.
[collectd.git] / src / rrdtool.c
1 /**
2  * collectd - src/rrdtool.c
3  * Copyright (C) 2006  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; either version 2 of the License, or (at your
8  * option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Florian octo Forster <octo at verplant.org>
21  **/
22
23 #include "collectd.h"
24 #include "plugin.h"
25 #include "common.h"
26
27 /*
28  * This weird macro cascade forces the glibc to define `NAN'. I don't know
29  * another way to solve this, so more intelligent solutions are welcome. -octo
30  */
31 #ifndef __USE_ISOC99
32 # define DISABLE__USE_ISOC99 1
33 # define __USE_ISOC99 1
34 #endif
35 #include <math.h>
36 #ifdef DISABLE__USE_ISOC99
37 # undef DISABLE__USE_ISOC99
38 # undef __USE_ISOC99
39 #endif
40
41 static int rra_timespans[] =
42 {
43         3600,
44         86400,
45         604800,
46         2678400,
47         31622400,
48         0
49 };
50 static int rra_timespans_num = 5;
51
52 static char *rra_types[] =
53 {
54         "AVERAGE",
55         "MIN",
56         "MAX",
57         NULL
58 };
59 static int rra_types_num = 3;
60
61 /* * * * * * * * * *
62  * WARNING:  Magic *
63  * * * * * * * * * */
64 static int rra_get (char ***ret)
65 {
66         static char **rra_def = NULL;
67         static int rra_num = 0;
68
69         int rra_max = rra_timespans_num * rra_types_num;
70
71         int step;
72         int rows;
73         int span;
74
75         int cdp_num;
76         int cdp_len;
77         int i, j;
78
79         char buffer[64];
80
81         if ((rra_num != 0) && (rra_def != NULL))
82         {
83                 *ret = rra_def;
84                 return (rra_num);
85         }
86
87         if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
88                 return (-1);
89         memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
90
91         step = atoi (COLLECTD_STEP);
92         rows = atoi (COLLECTD_ROWS);
93
94         if ((step <= 0) || (rows <= 0))
95         {
96                 *ret = NULL;
97                 return (-1);
98         }
99
100         cdp_len = 0;
101         for (i = 0; i < rra_timespans_num; i++)
102         {
103                 span = rra_timespans[i];
104
105                 if ((span / step) < rows)
106                         continue;
107
108                 if (cdp_len == 0)
109                         cdp_len = 1;
110                 else
111                         cdp_len = (int) floor (((double) span) / ((double) (rows * step)));
112
113                 cdp_num = (int) ceil (((double) span) / ((double) (cdp_len * step)));
114
115                 for (j = 0; j < rra_types_num; j++)
116                 {
117                         if (rra_num >= rra_max)
118                                 break;
119
120                         if (snprintf (buffer, sizeof(buffer), "RRA:%s:%3.1f:%u:%u",
121                                                 rra_types[j], COLLECTD_XFF,
122                                                 cdp_len, cdp_num) >= sizeof (buffer))
123                         {
124                                 syslog (LOG_ERR, "rra_get: Buffer would have been truncated.");
125                                 continue;
126                         }
127
128                         rra_def[rra_num++] = sstrdup (buffer);
129                 }
130         }
131
132 #if COLLECT_DEBUG
133         DBG ("rra_num = %i", rra_num);
134         for (i = 0; i < rra_num; i++)
135                 DBG ("  %s", rra_def[i]);
136 #endif
137
138         *ret = rra_def;
139         return (rra_num);
140 }
141
142 static void ds_free (int ds_num, char **ds_def)
143 {
144         int i;
145
146         for (i = 0; i < ds_num; i++)
147                 if (ds_def[i] != NULL)
148                         free (ds_def[i]);
149         free (ds_def);
150 }
151
152 static int ds_get (char ***ret, const data_set_t *ds)
153 {
154         char **ds_def;
155         int ds_num;
156
157         char min[32];
158         char max[32];
159         char buffer[128];
160
161         ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
162         if (ds_def == NULL)
163         {
164                 syslog (LOG_ERR, "rrdtool plugin: malloc failed: %s",
165                                 strerror (errno));
166                 return (-1);
167         }
168         memset (ds_def, '\0', ds->ds_num * sizeof (char *));
169
170         for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
171         {
172                 data_source_t *d = ds->ds + ds_num;
173                 char *type;
174                 int status;
175
176                 ds_def[ds_num] = NULL;
177
178                 if (d->type == DS_TYPE_COUNTER)
179                         type = "COUNTER";
180                 else if (d->type == DS_TYPE_GAUGE)
181                         type = "GAUGE";
182                 else
183                 {
184                         syslog (LOG_ERR, "rrdtool plugin: Unknown DS type: %i",
185                                         d->type);
186                         break;
187                 }
188
189                 if (d->min == NAN)
190                 {
191                         strcpy (min, "U");
192                 }
193                 else
194                 {
195                         snprintf (buffer, sizeof (min), "%lf", d->min);
196                         min[sizeof (min) - 1] = '\0';
197                 }
198
199                 if (d->max == NAN)
200                 {
201                         strcpy (max, "U");
202                 }
203                 else
204                 {
205                         snprintf (buffer, sizeof (max), "%lf", d->max);
206                         max[sizeof (max) - 1] = '\0';
207                 }
208
209                 status = snprintf (buffer, sizeof (buffer),
210                                 "DS:%s:%s:%s:%s:%s",
211                                 d->name, type, COLLECTD_HEARTBEAT,
212                                 min, max);
213                 if ((status < 1) || (status >= sizeof (buffer)))
214                         break;
215
216                 ds_def[ds_num] = sstrdup (buffer);
217                 ds_num++;
218         } /* for ds_num = 0 .. ds->ds_num */
219
220         if (ds_num != ds->ds_num)
221         {
222                 ds_free (ds_num, ds_def);
223                 return (-1);
224         }
225
226         *ret = ds_def;
227         return (ds_num);
228 }
229
230 static int rrd_create_file (char *filename, const data_set_t *ds)
231 {
232         char **argv;
233         int argc;
234         char **rra_def;
235         int rra_num;
236         char **ds_def;
237         int ds_num;
238         int i, j;
239         int status = 0;
240
241         if (check_create_dir (filename))
242                 return (-1);
243
244         if ((rra_num = rra_get (&rra_def)) < 1)
245         {
246                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate RRAs");
247                 return (-1);
248         }
249
250         if ((ds_num = ds_get (&ds_def, ds)) < 1)
251         {
252                 syslog (LOG_ERR, "rrd_create_file failed: Could not calculate DSes");
253                 return (-1);
254         }
255
256         argc = ds_num + rra_num + 4;
257
258         if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
259         {
260                 syslog (LOG_ERR, "rrd_create failed: %s", strerror (errno));
261                 return (-1);
262         }
263
264         argv[0] = "create";
265         argv[1] = filename;
266         argv[2] = "-s";
267         argv[3] = COLLECTD_STEP;
268
269         j = 4;
270         for (i = 0; i < ds_num; i++)
271                 argv[j++] = ds_def[i];
272         for (i = 0; i < rra_num; i++)
273                 argv[j++] = rra_def[i];
274         argv[j] = NULL;
275
276         optind = 0; /* bug in librrd? */
277         rrd_clear_error ();
278         if (rrd_create (argc, argv) == -1)
279         {
280                 syslog (LOG_ERR, "rrd_create failed: %s: %s", filename, rrd_get_error ());
281                 status = -1;
282         }
283
284         free (argv);
285         ds_free (ds_num, ds_def);
286
287         return (status);
288 }
289
290 static int value_list_to_string (char *buffer, int buffer_len,
291                 const data_set_t *ds, const value_list_t *vl)
292 {
293         int offset;
294         int status;
295         int i;
296
297         memset (buffer, '\0', sizeof (buffer_len));
298         buffer[0] = 'N';
299         offset = 1;
300
301         for (i = 0; i < ds->ds_num; i++)
302         {
303                 if ((ds->ds[i].type != DS_TYPE_COUNTER)
304                                 && (ds->ds[i].type != DS_TYPE_GAUGE))
305                         return (-1);
306
307                 if (ds->ds[i].type == DS_TYPE_COUNTER)
308                         status = snprintf (buffer + offset, buffer_len - offset,
309                                         ":%llu", vl->values[i].counter);
310                 else
311                         status = snprintf (buffer + offset, buffer_len - offset,
312                                         ":%lf", vl->values[i].gauge);
313
314                 if ((status < 1) || (status >= (buffer_len - offset)))
315                         return (-1);
316
317                 offset += status;
318         } /* for ds->ds_num */
319
320         return (0);
321 } /* int value_list_to_string */
322
323 static int value_list_to_filename (char *buffer, int buffer_len,
324                 const data_set_t *ds, const value_list_t *vl)
325 {
326         int offset = 0;
327         int status;
328
329         status = snprintf (buffer + offset, buffer_len - offset,
330                         "%s/", vl->host);
331         if ((status < 1) || (status >= buffer_len - offset))
332                 return (-1);
333         offset += status;
334
335         if (strlen (vl->plugin_instance) > 0)
336                 status = snprintf (buffer + offset, buffer_len - offset,
337                                 "%s-%s/", vl->plugin, vl->plugin_instance);
338         else
339                 status = snprintf (buffer + offset, buffer_len - offset,
340                                 "%s/", vl->plugin);
341         if ((status < 1) || (status >= buffer_len - offset))
342                 return (-1);
343         offset += status;
344
345         if (strlen (vl->type_instance) > 0)
346                 status = snprintf (buffer + offset, buffer_len - offset,
347                                 "%s-%s.rrd", ds->type, vl->type_instance);
348         else
349                 status = snprintf (buffer + offset, buffer_len - offset,
350                                 "%s.rrd", ds->type);
351         if ((status < 1) || (status >= buffer_len - offset))
352                 return (-1);
353         offset += status;
354
355         return (0);
356 } /* int value_list_to_filename */
357
358 static int rrd_write (const data_set_t *ds, const value_list_t *vl)
359 {
360         struct stat statbuf;
361         char        filename[512];
362         char        values[512];
363         char       *argv[4] = { "update", filename, values, NULL };
364
365         if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
366                 return (-1);
367
368         if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
369                 return (-1);
370
371         if (stat (filename, &statbuf) == -1)
372         {
373                 if (errno == ENOENT)
374                 {
375                         if (rrd_create_file (filename, ds))
376                                 return (-1);
377                 }
378                 else
379                 {
380                         syslog (LOG_ERR, "stat(%s) failed: %s",
381                                         filename, strerror (errno));
382                         return (-1);
383                 }
384         }
385         else if (!S_ISREG (statbuf.st_mode))
386         {
387                 syslog (LOG_ERR, "stat(%s): Not a regular file!",
388                                 filename);
389                 return (-1);
390         }
391
392         optind = 0; /* bug in librrd? */
393         rrd_clear_error ();
394         if (rrd_update (3, argv) == -1)
395         {
396                 syslog (LOG_WARNING, "rrd_update failed: %s: %s",
397                                 filename, rrd_get_error ());
398                 return (-1);
399         }
400         return (0);
401 } /* int rrd_update_file */
402
403 void module_register (void)
404 {
405         plugin_register_write ("rrdtool", rrd_write);
406 }