Merge branch 'collectd-5.4' into collectd-5.5
[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         } else {
287                 strunescape (tbl->sep, strlen (tbl->sep) + 1);
288         }
289
290         if (NULL == tbl->instance) {
291                 tbl->instance = sstrdup (tbl->file);
292                 replace_special (tbl->instance, strlen (tbl->instance));
293         }
294
295         if (NULL == tbl->results) {
296                 log_err ("Table \"%s\" does not specify any (valid) results.",
297                                 tbl->file);
298                 status = 1;
299         }
300
301         if (0 != status) {
302                 tbl_clear (tbl);
303                 --tables_num;
304                 return status;
305         }
306
307         for (i = 0; i < tbl->results_num; ++i) {
308                 tbl_result_t *res = tbl->results + i;
309                 size_t j;
310
311                 for (j = 0; j < res->instances_num; ++j)
312                         if (res->instances[j] > tbl->max_colnum)
313                                 tbl->max_colnum = res->instances[j];
314
315                 for (j = 0; j < res->values_num; ++j)
316                         if (res->values[j] > tbl->max_colnum)
317                                 tbl->max_colnum = res->values[j];
318         }
319         return 0;
320 } /* tbl_config_table */
321
322 static int tbl_config (oconfig_item_t *ci)
323 {
324         size_t i;
325
326         for (i = 0; i < ci->children_num; ++i) {
327                 oconfig_item_t *c = ci->children + i;
328
329                 if (0 == strcasecmp (c->key, "Table"))
330                         tbl_config_table (c);
331                 else
332                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
333         }
334         return 0;
335 } /* tbl_config */
336
337 /*
338  * result handling
339  */
340
341 static int tbl_prepare (tbl_t *tbl)
342 {
343         size_t i;
344
345         for (i = 0; i < tbl->results_num; ++i) {
346                 tbl_result_t *res = tbl->results + i;
347
348                 res->ds = plugin_get_ds (res->type);
349                 if (NULL == res->ds) {
350                         log_err ("Unknown type \"%s\". See types.db(5) for details.",
351                                         res->type);
352                         return -1;
353                 }
354
355                 if (res->values_num != (size_t)res->ds->ds_num) {
356                         log_err ("Invalid type \"%s\". Expected %zu data source%s, "
357                                         "got %i.", res->type, res->values_num,
358                                         (1 == res->values_num) ? "" : "s",
359                                         res->ds->ds_num);
360                         return -1;
361                 }
362         }
363         return 0;
364 } /* tbl_prepare */
365
366 static int tbl_finish (tbl_t *tbl)
367 {
368         size_t i;
369
370         for (i = 0; i < tbl->results_num; ++i)
371                 tbl->results[i].ds = NULL;
372         return 0;
373 } /* tbl_finish */
374
375 static int tbl_result_dispatch (tbl_t *tbl, tbl_result_t *res,
376                 char **fields, size_t fields_num)
377 {
378         value_list_t vl = VALUE_LIST_INIT;
379         value_t values[res->values_num];
380
381         size_t i;
382
383         assert (NULL != res->ds);
384         assert (res->values_num == res->ds->ds_num);
385
386         for (i = 0; i < res->values_num; ++i) {
387                 char *value;
388
389                 assert (res->values[i] < fields_num);
390                 value = fields[res->values[i]];
391
392                 if (0 != parse_value (value, &values[i], res->ds->ds[i].type))
393                         return -1;
394         }
395
396         vl.values     = values;
397         vl.values_len = STATIC_ARRAY_SIZE (values);
398
399         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
400         sstrncpy (vl.plugin, "table", sizeof (vl.plugin));
401         sstrncpy (vl.plugin_instance, tbl->instance, sizeof (vl.plugin_instance));
402         sstrncpy (vl.type, res->type, sizeof (vl.type));
403
404         if (0 == res->instances_num) {
405                 if (NULL != res->instance_prefix)
406                         sstrncpy (vl.type_instance, res->instance_prefix,
407                                         sizeof (vl.type_instance));
408         }
409         else {
410                 char *instances[res->instances_num];
411                 char  instances_str[DATA_MAX_NAME_LEN];
412
413                 for (i = 0; i < res->instances_num; ++i) {
414                         assert (res->instances[i] < fields_num);
415                         instances[i] = fields[res->instances[i]];
416                 }
417
418                 strjoin (instances_str, sizeof (instances_str),
419                                 instances, STATIC_ARRAY_SIZE (instances), "-");
420                 instances_str[sizeof (instances_str) - 1] = '\0';
421
422                 vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
423                 if (NULL == res->instance_prefix)
424                         strncpy (vl.type_instance, instances_str,
425                                         sizeof (vl.type_instance));
426                 else
427                         snprintf (vl.type_instance, sizeof (vl.type_instance),
428                                         "%s-%s", res->instance_prefix, instances_str);
429
430                 if ('\0' != vl.type_instance[sizeof (vl.type_instance) - 1]) {
431                         vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
432                         log_warn ("Truncated type instance: %s.", vl.type_instance);
433                 }
434         }
435
436         plugin_dispatch_values (&vl);
437         return 0;
438 } /* tbl_result_dispatch */
439
440 static int tbl_parse_line (tbl_t *tbl, char *line, size_t len)
441 {
442         char *fields[tbl->max_colnum + 1];
443         char *ptr, *saveptr;
444
445         size_t i;
446
447         i = 0;
448         ptr = line;
449         saveptr = NULL;
450         while (NULL != (fields[i] = strtok_r (ptr, tbl->sep, &saveptr))) {
451                 ptr = NULL;
452                 ++i;
453
454                 if (i > tbl->max_colnum)
455                         break;
456         }
457
458         if (i <= tbl->max_colnum) {
459                 log_err ("Not enough columns in line "
460                                 "(expected at least %zu, got %zu).",
461                                 tbl->max_colnum + 1, i);
462                 return -1;
463         }
464
465         for (i = 0; i < tbl->results_num; ++i)
466                 if (0 != tbl_result_dispatch (tbl, tbl->results + i,
467                                         fields, STATIC_ARRAY_SIZE (fields))) {
468                         log_err ("Failed to dispatch result.");
469                         continue;
470                 }
471         return 0;
472 } /* tbl_parse_line */
473
474 static int tbl_read_table (tbl_t *tbl)
475 {
476         FILE *fh;
477         char  buf[4096];
478
479         fh = fopen (tbl->file, "r");
480         if (NULL == fh) {
481                 char errbuf[1024];
482                 log_err ("Failed to open file \"%s\": %s.", tbl->file,
483                                 sstrerror (errno, errbuf, sizeof (errbuf)));
484                 return -1;
485         }
486
487         buf[sizeof (buf) - 1] = '\0';
488         while (NULL != fgets (buf, sizeof (buf), fh)) {
489                 if ('\0' != buf[sizeof (buf) - 1]) {
490                         buf[sizeof (buf) - 1] = '\0';
491                         log_err ("Table %s: Truncated line: %s", tbl->file, buf);
492                 }
493
494                 if (0 != tbl_parse_line (tbl, buf, sizeof (buf))) {
495                         log_err ("Table %s: Failed to parse line: %s", tbl->file, buf);
496                         continue;
497                 }
498         }
499
500         if (0 != ferror (fh)) {
501                 char errbuf[1024];
502                 log_err ("Failed to read from file \"%s\": %s.", tbl->file,
503                                 sstrerror (errno, errbuf, sizeof (errbuf)));
504                 fclose (fh);
505                 return -1;
506         }
507
508         fclose (fh);
509         return 0;
510 } /* tbl_read_table */
511
512 /*
513  * collectd callbacks
514  */
515
516 static int tbl_read (void)
517 {
518         int status = -1;
519         size_t i;
520
521         if (0 == tables_num)
522                 return 0;
523
524         for (i = 0; i < tables_num; ++i) {
525                 tbl_t *tbl = tables + i;
526
527                 if (0 != tbl_prepare (tbl)) {
528                         log_err ("Failed to prepare and parse table \"%s\".", tbl->file);
529                         continue;
530                 }
531
532                 if (0 == tbl_read_table (tbl))
533                         status = 0;
534
535                 tbl_finish (tbl);
536         }
537         return status;
538 } /* tbl_read */
539
540 static int tbl_shutdown (void)
541 {
542         size_t i;
543
544         for (i = 0; i < tables_num; ++i)
545                 tbl_clear (&tables[i]);
546         sfree (tables);
547         return 0;
548 } /* tbl_shutdown */
549
550 static int tbl_init (void)
551 {
552         if (0 == tables_num)
553                 return 0;
554
555         plugin_register_read ("table", tbl_read);
556         plugin_register_shutdown ("table", tbl_shutdown);
557         return 0;
558 } /* tbl_init */
559
560 void module_register (void)
561 {
562         plugin_register_complex_config ("table", tbl_config);
563         plugin_register_init ("table", tbl_init);
564 } /* module_register */
565
566 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */