{GPL, other}: Relicense to MIT license.
[collectd.git] / src / table.c
1 /**
2  * collectd - src/table.c
3  * Copyright (C) 2009       Sebastian Harl
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Sebastian Harl <sh at tokkee.org>
25  **/
26
27 /*
28  * This module provides generic means to parse and dispatch tabular data.
29  */
30
31 #include "collectd.h"
32 #include "common.h"
33
34 #include "configfile.h"
35 #include "plugin.h"
36
37 #define log_err(...) ERROR ("table plugin: " __VA_ARGS__)
38 #define log_warn(...) WARNING ("table plugin: " __VA_ARGS__)
39
40 /*
41  * private data types
42  */
43
44 typedef struct {
45         char  *type;
46         char  *instance_prefix;
47         int   *instances;
48         size_t instances_num;
49         int   *values;
50         size_t values_num;
51
52         const data_set_t *ds;
53 } tbl_result_t;
54
55 typedef struct {
56         char *file;
57         char *sep;
58         char *instance;
59
60         tbl_result_t *results;
61         size_t        results_num;
62
63         size_t max_colnum;
64 } tbl_t;
65
66 static void tbl_result_setup (tbl_result_t *res)
67 {
68         res->type            = NULL;
69
70         res->instance_prefix = NULL;
71         res->instances       = NULL;
72         res->instances_num   = 0;
73
74         res->values          = NULL;
75         res->values_num      = 0;
76
77         res->ds              = NULL;
78 } /* tbl_result_setup */
79
80 static void tbl_result_clear (tbl_result_t *res)
81 {
82         sfree (res->type);
83
84         sfree (res->instance_prefix);
85         sfree (res->instances);
86         res->instances_num = 0;
87
88         sfree (res->values);
89         res->values_num = 0;
90
91         res->ds = NULL;
92 } /* tbl_result_clear */
93
94 static void tbl_setup (tbl_t *tbl, char *file)
95 {
96         tbl->file        = sstrdup (file);
97         tbl->sep         = NULL;
98         tbl->instance    = NULL;
99
100         tbl->results     = NULL;
101         tbl->results_num = 0;
102
103         tbl->max_colnum  = 0;
104 } /* tbl_setup */
105
106 static void tbl_clear (tbl_t *tbl)
107 {
108         size_t i;
109
110         sfree (tbl->file);
111         sfree (tbl->sep);
112         sfree (tbl->instance);
113
114         for (i = 0; i < tbl->results_num; ++i)
115                 tbl_result_clear (tbl->results + i);
116         sfree (tbl->results);
117         tbl->results_num = 0;
118
119         tbl->max_colnum  = 0;
120 } /* tbl_clear */
121
122 static tbl_t *tables;
123 static size_t tables_num;
124
125 /*
126  * configuration handling
127  */
128
129 static int tbl_config_set_s (char *name, char **var, oconfig_item_t *ci)
130 {
131         if ((1 != ci->values_num)
132                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
133                 log_err ("\"%s\" expects a single string argument.", name);
134                 return 1;
135         }
136
137         sfree (*var);
138         *var = sstrdup (ci->values[0].value.string);
139         return 0;
140 } /* tbl_config_set_separator */
141
142 static int tbl_config_append_array_i (char *name, int **var, size_t *len,
143                 oconfig_item_t *ci)
144 {
145         int *tmp;
146
147         size_t i;
148
149         if (1 > ci->values_num) {
150                 log_err ("\"%s\" expects at least one argument.", name);
151                 return 1;
152         }
153
154         for (i = 0; i < ci->values_num; ++i) {
155                 if (OCONFIG_TYPE_NUMBER != ci->values[i].type) {
156                         log_err ("\"%s\" expects numerical arguments only.", name);
157                         return 1;
158                 }
159         }
160
161         *len += ci->values_num;
162         tmp = (int *)realloc (*var, *len * sizeof (**var));
163         if (NULL == tmp) {
164                 char errbuf[1024];
165                 log_err ("realloc failed: %s.",
166                                 sstrerror (errno, errbuf, sizeof (errbuf)));
167                 return -1;
168         }
169
170         *var = tmp;
171
172         for (i = *len - ci->values_num; i < *len; ++i)
173                 (*var)[i] = (int)ci->values[i].value.number;
174         return 0;
175 } /* tbl_config_append_array_s */
176
177 static int tbl_config_result (tbl_t *tbl, oconfig_item_t *ci)
178 {
179         tbl_result_t *res;
180
181         int status = 0;
182         size_t i;
183
184         if (0 != ci->values_num) {
185                 log_err ("<Result> does not expect any arguments.");
186                 return 1;
187         }
188
189         res = (tbl_result_t *)realloc (tbl->results,
190                         (tbl->results_num + 1) * sizeof (*tbl->results));
191         if (NULL == tbl) {
192                 char errbuf[1024];
193                 log_err ("realloc failed: %s.",
194                                 sstrerror (errno, errbuf, sizeof (errbuf)));
195                 return -1;
196         }
197
198         tbl->results = res;
199         ++tbl->results_num;
200
201         res = tbl->results + tbl->results_num - 1;
202         tbl_result_setup (res);
203
204         for (i = 0; i < ci->children_num; ++i) {
205                 oconfig_item_t *c = ci->children + i;
206
207                 if (0 == strcasecmp (c->key, "Type"))
208                         tbl_config_set_s (c->key, &res->type, c);
209                 else if (0 == strcasecmp (c->key, "InstancePrefix"))
210                         tbl_config_set_s (c->key, &res->instance_prefix, c);
211                 else if (0 == strcasecmp (c->key, "InstancesFrom"))
212                         tbl_config_append_array_i (c->key,
213                                         &res->instances, &res->instances_num, c);
214                 else if (0 == strcasecmp (c->key, "ValuesFrom"))
215                         tbl_config_append_array_i (c->key,
216                                         &res->values, &res->values_num, c);
217                 else
218                         log_warn ("Ignoring unknown config key \"%s\" "
219                                         " in <Result>.", c->key);
220         }
221
222         if (NULL == res->type) {
223                 log_err ("No \"Type\" option specified for <Result> "
224                                 "in table \"%s\".", tbl->file);
225                 status = 1;
226         }
227
228         if (NULL == res->values) {
229                 log_err ("No \"ValuesFrom\" option specified for <Result> "
230                                 "in table \"%s\".", tbl->file);
231                 status = 1;
232         }
233
234         if (0 != status) {
235                 tbl_result_clear (res);
236                 --tbl->results_num;
237                 return status;
238         }
239         return 0;
240 } /* tbl_config_result */
241
242 static int tbl_config_table (oconfig_item_t *ci)
243 {
244         tbl_t *tbl;
245
246         int status = 0;
247         size_t i;
248
249         if ((1 != ci->values_num)
250                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
251                 log_err ("<Table> expects a single string argument.");
252                 return 1;
253         }
254
255         tbl = (tbl_t *)realloc (tables, (tables_num + 1) * sizeof (*tables));
256         if (NULL == tbl) {
257                 char errbuf[1024];
258                 log_err ("realloc failed: %s.",
259                                 sstrerror (errno, errbuf, sizeof (errbuf)));
260                 return -1;
261         }
262
263         tables = tbl;
264         ++tables_num;
265
266         tbl = tables + tables_num - 1;
267         tbl_setup (tbl, ci->values[0].value.string);
268
269         for (i = 0; i < ci->children_num; ++i) {
270                 oconfig_item_t *c = ci->children + i;
271
272                 if (0 == strcasecmp (c->key, "Separator"))
273                         tbl_config_set_s (c->key, &tbl->sep, c);
274                 else if (0 == strcasecmp (c->key, "Instance"))
275                         tbl_config_set_s (c->key, &tbl->instance, c);
276                 else if (0 == strcasecmp (c->key, "Result"))
277                         tbl_config_result (tbl, c);
278                 else
279                         log_warn ("Ignoring unknown config key \"%s\" "
280                                         "in <Table %s>.", c->key, tbl->file);
281         }
282
283         if (NULL == tbl->sep) {
284                 log_err ("Table \"%s\" does not specify any separator.", tbl->file);
285                 status = 1;
286         }
287         strunescape (tbl->sep, strlen (tbl->sep) + 1);
288
289         if (NULL == tbl->instance) {
290                 tbl->instance = sstrdup (tbl->file);
291                 replace_special (tbl->instance, strlen (tbl->instance));
292         }
293
294         if (NULL == tbl->results) {
295                 log_err ("Table \"%s\" does not specify any (valid) results.",
296                                 tbl->file);
297                 status = 1;
298         }
299
300         if (0 != status) {
301                 tbl_clear (tbl);
302                 --tables_num;
303                 return status;
304         }
305
306         for (i = 0; i < tbl->results_num; ++i) {
307                 tbl_result_t *res = tbl->results + i;
308                 size_t j;
309
310                 for (j = 0; j < res->instances_num; ++j)
311                         if (res->instances[j] > tbl->max_colnum)
312                                 tbl->max_colnum = res->instances[j];
313
314                 for (j = 0; j < res->values_num; ++j)
315                         if (res->values[j] > tbl->max_colnum)
316                                 tbl->max_colnum = res->values[j];
317         }
318         return 0;
319 } /* tbl_config_table */
320
321 static int tbl_config (oconfig_item_t *ci)
322 {
323         size_t i;
324
325         for (i = 0; i < ci->children_num; ++i) {
326                 oconfig_item_t *c = ci->children + i;
327
328                 if (0 == strcasecmp (c->key, "Table"))
329                         tbl_config_table (c);
330                 else
331                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
332         }
333         return 0;
334 } /* tbl_config */
335
336 /*
337  * result handling
338  */
339
340 static int tbl_prepare (tbl_t *tbl)
341 {
342         size_t i;
343
344         for (i = 0; i < tbl->results_num; ++i) {
345                 tbl_result_t *res = tbl->results + i;
346
347                 res->ds = plugin_get_ds (res->type);
348                 if (NULL == res->ds) {
349                         log_err ("Unknown type \"%s\". See types.db(5) for details.",
350                                         res->type);
351                         return -1;
352                 }
353
354                 if (res->values_num != (size_t)res->ds->ds_num) {
355                         log_err ("Invalid type \"%s\". Expected %zu data source%s, "
356                                         "got %i.", res->type, res->values_num,
357                                         (1 == res->values_num) ? "" : "s",
358                                         res->ds->ds_num);
359                         return -1;
360                 }
361         }
362         return 0;
363 } /* tbl_prepare */
364
365 static int tbl_finish (tbl_t *tbl)
366 {
367         size_t i;
368
369         for (i = 0; i < tbl->results_num; ++i)
370                 tbl->results[i].ds = NULL;
371         return 0;
372 } /* tbl_finish */
373
374 static int tbl_result_dispatch (tbl_t *tbl, tbl_result_t *res,
375                 char **fields, size_t fields_num)
376 {
377         value_list_t vl = VALUE_LIST_INIT;
378         value_t values[res->values_num];
379
380         size_t i;
381
382         assert (NULL != res->ds);
383         assert (res->values_num == res->ds->ds_num);
384
385         for (i = 0; i < res->values_num; ++i) {
386                 char *value;
387
388                 assert (res->values[i] < fields_num);
389                 value = fields[res->values[i]];
390
391                 if (0 != parse_value (value, &values[i], res->ds->ds[i].type))
392                         return -1;
393         }
394
395         vl.values     = values;
396         vl.values_len = STATIC_ARRAY_SIZE (values);
397
398         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
399         sstrncpy (vl.plugin, "table", sizeof (vl.plugin));
400         sstrncpy (vl.plugin_instance, tbl->instance, sizeof (vl.plugin_instance));
401         sstrncpy (vl.type, res->type, sizeof (vl.type));
402
403         if (0 == res->instances_num) {
404                 if (NULL != res->instance_prefix)
405                         sstrncpy (vl.type_instance, res->instance_prefix,
406                                         sizeof (vl.type_instance));
407         }
408         else {
409                 char *instances[res->instances_num];
410                 char  instances_str[DATA_MAX_NAME_LEN];
411
412                 for (i = 0; i < res->instances_num; ++i) {
413                         assert (res->instances[i] < fields_num);
414                         instances[i] = fields[res->instances[i]];
415                 }
416
417                 strjoin (instances_str, sizeof (instances_str),
418                                 instances, STATIC_ARRAY_SIZE (instances), "-");
419                 instances_str[sizeof (instances_str) - 1] = '\0';
420
421                 vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
422                 if (NULL == res->instance_prefix)
423                         strncpy (vl.type_instance, instances_str,
424                                         sizeof (vl.type_instance));
425                 else
426                         snprintf (vl.type_instance, sizeof (vl.type_instance),
427                                         "%s-%s", res->instance_prefix, instances_str);
428
429                 if ('\0' != vl.type_instance[sizeof (vl.type_instance) - 1]) {
430                         vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
431                         log_warn ("Truncated type instance: %s.", vl.type_instance);
432                 }
433         }
434
435         plugin_dispatch_values (&vl);
436         return 0;
437 } /* tbl_result_dispatch */
438
439 static int tbl_parse_line (tbl_t *tbl, char *line, size_t len)
440 {
441         char *fields[tbl->max_colnum + 1];
442         char *ptr, *saveptr;
443
444         size_t i;
445
446         i = 0;
447         ptr = line;
448         saveptr = NULL;
449         while (NULL != (fields[i] = strtok_r (ptr, tbl->sep, &saveptr))) {
450                 ptr = NULL;
451                 ++i;
452
453                 if (i > tbl->max_colnum)
454                         break;
455         }
456
457         if (i <= tbl->max_colnum) {
458                 log_err ("Not enough columns in line "
459                                 "(expected at least %zu, got %zu).",
460                                 tbl->max_colnum + 1, i);
461                 return -1;
462         }
463
464         for (i = 0; i < tbl->results_num; ++i)
465                 if (0 != tbl_result_dispatch (tbl, tbl->results + i,
466                                         fields, STATIC_ARRAY_SIZE (fields))) {
467                         log_err ("Failed to dispatch result.");
468                         continue;
469                 }
470         return 0;
471 } /* tbl_parse_line */
472
473 static int tbl_read_table (tbl_t *tbl)
474 {
475         FILE *fh;
476         char  buf[4096];
477
478         fh = fopen (tbl->file, "r");
479         if (NULL == fh) {
480                 char errbuf[1024];
481                 log_err ("Failed to open file \"%s\": %s.", tbl->file,
482                                 sstrerror (errno, errbuf, sizeof (errbuf)));
483                 return -1;
484         }
485
486         buf[sizeof (buf) - 1] = '\0';
487         while (NULL != fgets (buf, sizeof (buf), fh)) {
488                 if ('\0' != buf[sizeof (buf) - 1]) {
489                         buf[sizeof (buf) - 1] = '\0';
490                         log_err ("Table %s: Truncated line: %s", tbl->file, buf);
491                 }
492
493                 if (0 != tbl_parse_line (tbl, buf, sizeof (buf))) {
494                         log_err ("Table %s: Failed to parse line: %s", tbl->file, buf);
495                         continue;
496                 }
497         }
498
499         if (0 != ferror (fh)) {
500                 char errbuf[1024];
501                 log_err ("Failed to read from file \"%s\": %s.", tbl->file,
502                                 sstrerror (errno, errbuf, sizeof (errbuf)));
503                 fclose (fh);
504                 return -1;
505         }
506
507         fclose (fh);
508         return 0;
509 } /* tbl_read_table */
510
511 /*
512  * collectd callbacks
513  */
514
515 static int tbl_read (void)
516 {
517         int status = -1;
518         size_t i;
519
520         if (0 == tables_num)
521                 return 0;
522
523         for (i = 0; i < tables_num; ++i) {
524                 tbl_t *tbl = tables + i;
525
526                 if (0 != tbl_prepare (tbl)) {
527                         log_err ("Failed to prepare and parse table \"%s\".", tbl->file);
528                         continue;
529                 }
530
531                 if (0 == tbl_read_table (tbl))
532                         status = 0;
533
534                 tbl_finish (tbl);
535         }
536         return status;
537 } /* tbl_read */
538
539 static int tbl_shutdown (void)
540 {
541         size_t i;
542
543         for (i = 0; i < tables_num; ++i)
544                 tbl_clear (&tables[i]);
545         sfree (tables);
546         return 0;
547 } /* tbl_shutdown */
548
549 static int tbl_init (void)
550 {
551         if (0 == tables_num)
552                 return 0;
553
554         plugin_register_read ("table", tbl_read);
555         plugin_register_shutdown ("table", tbl_shutdown);
556         return 0;
557 } /* tbl_init */
558
559 void module_register (void)
560 {
561         plugin_register_complex_config ("table", tbl_config);
562         plugin_register_init ("table", tbl_init);
563 } /* module_register */
564
565 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */