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
33 #include "utils/common/common.h"
34
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 size_t *instances;
48 size_t instances_num;
49 size_t *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 *plugin_name;
59 char *instance;
60
61 tbl_result_t *results;
62 size_t results_num;
63
64 size_t max_colnum;
65 } tbl_t;
66
tbl_result_setup(tbl_result_t * res)67 static void tbl_result_setup(tbl_result_t *res) {
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
tbl_result_clear(tbl_result_t * res)80 static void tbl_result_clear(tbl_result_t *res) {
81 if (res == NULL) {
82 return;
83 }
84
85 sfree(res->type);
86
87 sfree(res->instance_prefix);
88 sfree(res->instances);
89 res->instances_num = 0;
90
91 sfree(res->values);
92 res->values_num = 0;
93
94 res->ds = NULL;
95 } /* tbl_result_clear */
96
tbl_setup(tbl_t * tbl,char * file)97 static void tbl_setup(tbl_t *tbl, char *file) {
98 tbl->file = sstrdup(file);
99 tbl->sep = NULL;
100 tbl->plugin_name = NULL;
101 tbl->instance = NULL;
102
103 tbl->results = NULL;
104 tbl->results_num = 0;
105
106 tbl->max_colnum = 0;
107 } /* tbl_setup */
108
tbl_clear(tbl_t * tbl)109 static void tbl_clear(tbl_t *tbl) {
110 if (tbl == NULL) {
111 return;
112 }
113
114 sfree(tbl->file);
115 sfree(tbl->sep);
116 sfree(tbl->plugin_name);
117 sfree(tbl->instance);
118
119 /* (tbl->results == NULL) -> (tbl->results_num == 0) */
120 assert((tbl->results != NULL) || (tbl->results_num == 0));
121 for (size_t i = 0; i < tbl->results_num; ++i)
122 tbl_result_clear(tbl->results + i);
123 sfree(tbl->results);
124 tbl->results_num = 0;
125
126 tbl->max_colnum = 0;
127 } /* tbl_clear */
128
129 static tbl_t *tables;
130 static size_t tables_num;
131
132 /*
133 * configuration handling
134 */
tbl_config_append_array_i(char * name,size_t ** var,size_t * len,oconfig_item_t * ci)135 static int tbl_config_append_array_i(char *name, size_t **var, size_t *len,
136 oconfig_item_t *ci) {
137 if (ci->values_num < 1) {
138 log_err("\"%s\" expects at least one argument.", name);
139 return 1;
140 }
141
142 size_t num = ci->values_num;
143 for (size_t i = 0; i < num; ++i) {
144 if (OCONFIG_TYPE_NUMBER != ci->values[i].type) {
145 log_err("\"%s\" expects numerical arguments only.", name);
146 return 1;
147 }
148 }
149
150 size_t *tmp = realloc(*var, ((*len) + num) * sizeof(**var));
151 if (tmp == NULL) {
152 log_err("realloc failed: %s.", STRERRNO);
153 return -1;
154 }
155 *var = tmp;
156
157 for (size_t i = 0; i < num; ++i) {
158 (*var)[*len] = (size_t)ci->values[i].value.number;
159 (*len)++;
160 }
161
162 return 0;
163 } /* tbl_config_append_array_s */
164
tbl_config_result(tbl_t * tbl,oconfig_item_t * ci)165 static int tbl_config_result(tbl_t *tbl, oconfig_item_t *ci) {
166 if (ci->values_num != 0) {
167 log_err("<Result> does not expect any arguments.");
168 return 1;
169 }
170
171 tbl_result_t *res =
172 realloc(tbl->results, (tbl->results_num + 1) * sizeof(*tbl->results));
173 if (res == NULL) {
174 log_err("realloc failed: %s.", STRERRNO);
175 return -1;
176 }
177
178 tbl->results = res;
179
180 res = tbl->results + tbl->results_num;
181 tbl_result_setup(res);
182
183 for (int i = 0; i < ci->children_num; ++i) {
184 oconfig_item_t *c = ci->children + i;
185
186 if (strcasecmp(c->key, "Type") == 0)
187 cf_util_get_string(c, &res->type);
188 else if (strcasecmp(c->key, "InstancePrefix") == 0)
189 cf_util_get_string(c, &res->instance_prefix);
190 else if (strcasecmp(c->key, "InstancesFrom") == 0)
191 tbl_config_append_array_i(c->key, &res->instances, &res->instances_num,
192 c);
193 else if (strcasecmp(c->key, "ValuesFrom") == 0)
194 tbl_config_append_array_i(c->key, &res->values, &res->values_num, c);
195 else
196 log_warn("Ignoring unknown config key \"%s\" "
197 " in <Result>.",
198 c->key);
199 }
200
201 int status = 0;
202 if (res->type == NULL) {
203 log_err("No \"Type\" option specified for <Result> in table \"%s\".",
204 tbl->file);
205 status = 1;
206 }
207
208 if (res->values == NULL) {
209 log_err("No \"ValuesFrom\" option specified for <Result> in table \"%s\".",
210 tbl->file);
211 status = 1;
212 }
213
214 if (status != 0) {
215 tbl_result_clear(res);
216 return status;
217 }
218
219 tbl->results_num++;
220 return 0;
221 } /* tbl_config_result */
222
tbl_config_table(oconfig_item_t * ci)223 static int tbl_config_table(oconfig_item_t *ci) {
224 if (ci->values_num != 1 || ci->values[0].type != OCONFIG_TYPE_STRING) {
225 log_err("<Table> expects a single string argument.");
226 return 1;
227 }
228
229 tbl_t *tbl = realloc(tables, (tables_num + 1) * sizeof(*tables));
230 if (tbl == NULL) {
231 log_err("realloc failed: %s.", STRERRNO);
232 return -1;
233 }
234
235 tables = tbl;
236
237 tbl = tables + tables_num;
238 tbl_setup(tbl, ci->values[0].value.string);
239
240 for (int i = 0; i < ci->children_num; i++) {
241 oconfig_item_t *c = ci->children + i;
242
243 if (strcasecmp(c->key, "Separator") == 0)
244 cf_util_get_string(c, &tbl->sep);
245 else if (strcasecmp(c->key, "Plugin") == 0)
246 cf_util_get_string(c, &tbl->plugin_name);
247 else if (strcasecmp(c->key, "Instance") == 0)
248 cf_util_get_string(c, &tbl->instance);
249 else if (strcasecmp(c->key, "Result") == 0)
250 tbl_config_result(tbl, c);
251 else
252 log_warn("Ignoring unknown config key \"%s\" "
253 "in <Table %s>.",
254 c->key, tbl->file);
255 }
256
257 int status = 0;
258 if (tbl->sep == NULL) {
259 log_err("Table \"%s\" does not specify any separator.", tbl->file);
260 status = 1;
261 } else {
262 strunescape(tbl->sep, strlen(tbl->sep) + 1);
263 }
264
265 if (tbl->instance == NULL) {
266 tbl->instance = sstrdup(tbl->file);
267 replace_special(tbl->instance, strlen(tbl->instance));
268 }
269
270 if (tbl->results == NULL) {
271 assert(tbl->results_num == 0);
272 log_err("Table \"%s\" does not specify any (valid) results.", tbl->file);
273 status = 1;
274 }
275
276 if (status != 0) {
277 tbl_clear(tbl);
278 return status;
279 }
280
281 for (size_t i = 0; i < tbl->results_num; ++i) {
282 tbl_result_t *res = tbl->results + i;
283
284 for (size_t j = 0; j < res->instances_num; ++j)
285 if (res->instances[j] > tbl->max_colnum)
286 tbl->max_colnum = res->instances[j];
287
288 for (size_t j = 0; j < res->values_num; ++j)
289 if (res->values[j] > tbl->max_colnum)
290 tbl->max_colnum = res->values[j];
291 }
292
293 tables_num++;
294 return 0;
295 } /* tbl_config_table */
296
tbl_config(oconfig_item_t * ci)297 static int tbl_config(oconfig_item_t *ci) {
298 for (int i = 0; i < ci->children_num; ++i) {
299 oconfig_item_t *c = ci->children + i;
300
301 if (strcasecmp(c->key, "Table") == 0)
302 tbl_config_table(c);
303 else
304 log_warn("Ignoring unknown config key \"%s\".", c->key);
305 }
306 return 0;
307 } /* tbl_config */
308
309 /*
310 * result handling
311 */
312
tbl_prepare(tbl_t * tbl)313 static int tbl_prepare(tbl_t *tbl) {
314 for (size_t i = 0; i < tbl->results_num; ++i) {
315 tbl_result_t *res = tbl->results + i;
316
317 res->ds = plugin_get_ds(res->type);
318 if (res->ds == NULL) {
319 log_err("Unknown type \"%s\". See types.db(5) for details.", res->type);
320 return -1;
321 }
322
323 if (res->values_num != res->ds->ds_num) {
324 log_err("Invalid type \"%s\". Expected %" PRIsz " data source%s, "
325 "got %" PRIsz ".",
326 res->type, res->values_num, (1 == res->values_num) ? "" : "s",
327 res->ds->ds_num);
328 return -1;
329 }
330 }
331 return 0;
332 } /* tbl_prepare */
333
tbl_finish(tbl_t * tbl)334 static int tbl_finish(tbl_t *tbl) {
335 for (size_t i = 0; i < tbl->results_num; ++i)
336 tbl->results[i].ds = NULL;
337 return 0;
338 } /* tbl_finish */
339
tbl_result_dispatch(tbl_t * tbl,tbl_result_t * res,char ** fields,size_t fields_num)340 static int tbl_result_dispatch(tbl_t *tbl, tbl_result_t *res, char **fields,
341 size_t fields_num) {
342 value_list_t vl = VALUE_LIST_INIT;
343 value_t values[res->values_num];
344
345 assert(res->ds);
346 assert(res->values_num == res->ds->ds_num);
347
348 for (size_t i = 0; i < res->values_num; ++i) {
349 assert(res->values[i] < fields_num);
350 char *value = fields[res->values[i]];
351 if (parse_value(value, &values[i], res->ds->ds[i].type) != 0)
352 return -1;
353 }
354
355 vl.values = values;
356 vl.values_len = STATIC_ARRAY_SIZE(values);
357
358 sstrncpy(vl.plugin, (tbl->plugin_name != NULL) ? tbl->plugin_name : "table",
359 sizeof(vl.plugin));
360 sstrncpy(vl.plugin_instance, tbl->instance, sizeof(vl.plugin_instance));
361 sstrncpy(vl.type, res->type, sizeof(vl.type));
362
363 if (res->instances_num == 0) {
364 if (res->instance_prefix)
365 sstrncpy(vl.type_instance, res->instance_prefix,
366 sizeof(vl.type_instance));
367 } else {
368 char *instances[res->instances_num];
369 char instances_str[DATA_MAX_NAME_LEN];
370
371 for (size_t i = 0; i < res->instances_num; ++i) {
372 assert(res->instances[i] < fields_num);
373 instances[i] = fields[res->instances[i]];
374 }
375
376 strjoin(instances_str, sizeof(instances_str), instances,
377 STATIC_ARRAY_SIZE(instances), "-");
378 instances_str[sizeof(instances_str) - 1] = '\0';
379
380 int r;
381 if (res->instance_prefix == NULL)
382 r = snprintf(vl.type_instance, sizeof(vl.type_instance), "%s",
383 instances_str);
384 else
385 r = snprintf(vl.type_instance, sizeof(vl.type_instance), "%s-%s",
386 res->instance_prefix, instances_str);
387 if ((size_t)r >= sizeof(vl.type_instance))
388 log_warn("Truncated type instance: %s.", vl.type_instance);
389 }
390
391 plugin_dispatch_values(&vl);
392 return 0;
393 } /* tbl_result_dispatch */
394
tbl_parse_line(tbl_t * tbl,char * line,size_t len)395 static int tbl_parse_line(tbl_t *tbl, char *line, size_t len) {
396 char *fields[tbl->max_colnum + 1];
397 size_t i = 0;
398
399 char *ptr = line;
400 char *saveptr = NULL;
401 while ((fields[i] = strtok_r(ptr, tbl->sep, &saveptr)) != NULL) {
402 ptr = NULL;
403 i++;
404
405 if (i > tbl->max_colnum)
406 break;
407 }
408
409 if (i <= tbl->max_colnum) {
410 log_warn("Not enough columns in line "
411 "(expected at least %" PRIsz ", got %" PRIsz ").",
412 tbl->max_colnum + 1, i);
413 return -1;
414 }
415
416 for (i = 0; i < tbl->results_num; ++i)
417 if (tbl_result_dispatch(tbl, tbl->results + i, fields,
418 STATIC_ARRAY_SIZE(fields)) != 0) {
419 log_err("Failed to dispatch result.");
420 continue;
421 }
422 return 0;
423 } /* tbl_parse_line */
424
tbl_read_table(tbl_t * tbl)425 static int tbl_read_table(tbl_t *tbl) {
426 char buf[4096];
427
428 FILE *fh = fopen(tbl->file, "r");
429 if (fh == NULL) {
430 log_err("Failed to open file \"%s\": %s.", tbl->file, STRERRNO);
431 return -1;
432 }
433
434 buf[sizeof(buf) - 1] = '\0';
435 while (fgets(buf, sizeof(buf), fh) != NULL) {
436 if (buf[sizeof(buf) - 1] != '\0') {
437 buf[sizeof(buf) - 1] = '\0';
438 log_warn("Table %s: Truncated line: %s", tbl->file, buf);
439 }
440
441 if (tbl_parse_line(tbl, buf, sizeof(buf)) != 0) {
442 log_warn("Table %s: Failed to parse line: %s", tbl->file, buf);
443 continue;
444 }
445 }
446
447 if (ferror(fh) != 0) {
448 log_err("Failed to read from file \"%s\": %s.", tbl->file, STRERRNO);
449 fclose(fh);
450 return -1;
451 }
452
453 fclose(fh);
454 return 0;
455 } /* tbl_read_table */
456
457 /*
458 * collectd callbacks
459 */
460
tbl_read(void)461 static int tbl_read(void) {
462 int status = -1;
463
464 if (tables_num == 0)
465 return 0;
466
467 for (size_t i = 0; i < tables_num; ++i) {
468 tbl_t *tbl = tables + i;
469
470 if (tbl_prepare(tbl) != 0) {
471 log_err("Failed to prepare and parse table \"%s\".", tbl->file);
472 continue;
473 }
474
475 if (tbl_read_table(tbl) == 0)
476 status = 0;
477
478 tbl_finish(tbl);
479 }
480 return status;
481 } /* tbl_read */
482
tbl_shutdown(void)483 static int tbl_shutdown(void) {
484 for (size_t i = 0; i < tables_num; ++i)
485 tbl_clear(&tables[i]);
486 sfree(tables);
487 return 0;
488 } /* tbl_shutdown */
489
tbl_init(void)490 static int tbl_init(void) {
491 if (tables_num == 0)
492 return 0;
493
494 plugin_register_read("table", tbl_read);
495 plugin_register_shutdown("table", tbl_shutdown);
496 return 0;
497 } /* tbl_init */
498
module_register(void)499 void module_register(void) {
500 plugin_register_complex_config("table", tbl_config);
501 plugin_register_init("table", tbl_init);
502 } /* module_register */
503