2895dc86ff51053429bae5539c20e2372ac32c37
1 /**
2 * collectd - src/collectdctl-show.c
3 * Copyright (C) 2011 Florian 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; only version 2 of the License is applicable.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 * Authors:
19 * Florian "octo" Forster <octo at collectd.org>
20 **/
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <strings.h>
32 #include <assert.h>
33 #include <errno.h>
35 #if NAN_STATIC_DEFAULT
36 # include <math.h>
37 /* #endif NAN_STATIC_DEFAULT*/
38 #elif NAN_STATIC_ISOC
39 # ifndef __USE_ISOC99
40 # define DISABLE_ISOC99 1
41 # define __USE_ISOC99 1
42 # endif /* !defined(__USE_ISOC99) */
43 # include <math.h>
44 # if DISABLE_ISOC99
45 # undef DISABLE_ISOC99
46 # undef __USE_ISOC99
47 # endif /* DISABLE_ISOC99 */
48 /* #endif NAN_STATIC_ISOC */
49 #elif NAN_ZERO_ZERO
50 # include <math.h>
51 # ifdef NAN
52 # undef NAN
53 # endif
54 # define NAN (0.0 / 0.0)
55 # ifndef isnan
56 # define isnan(f) ((f) != (f))
57 # endif /* !defined(isnan) */
58 # ifndef isfinite
59 # define isfinite(f) (((f) - (f)) == 0.0)
60 # endif
61 # ifndef isinf
62 # define isinf(f) (!isfinite(f) && !isnan(f))
63 # endif
64 #endif /* NAN_ZERO_ZERO */
66 #include "libcollectdclient/client.h"
68 #define AGGR_TYPE_COUNT 0
69 #define AGGR_TYPE_MIN 1
70 #define AGGR_TYPE_MAX 2
71 #define AGGR_TYPE_AVG 3
72 #define AGGR_TYPE_SUM 4
73 #define AGGR_TYPE_SDEV 5
75 /*
76 * Data structures
77 */
78 struct aggregation_group_s
79 {
80 char *name;
82 int num;
83 double min;
84 double max;
85 double sum;
86 double sum_of_squares;
87 };
88 typedef struct aggregation_group_s aggregation_group_t;
90 /*
91 * Global variables
92 */
93 /* Selection */
94 static const char *re_host = NULL;
95 static const char *re_plugin = NULL;
96 static const char *re_plugin_instance = NULL;
97 static const char *re_type = NULL;
98 static const char *re_type_instance = NULL;
100 /* Grouping */
101 static uint16_t grouping = 0;
103 /* Aggregation */
104 static int *aggregation_types = NULL;
105 static size_t aggregation_types_num = 0;
107 static aggregation_group_t *aggregation_groups = NULL;
108 static size_t aggregation_groups_num = 0;
110 /*
111 * Private functions
112 */
113 static int parse_aggr_type (const char *type) /* {{{ */
114 {
115 if (type == NULL)
116 return (-1);
117 else if (strcasecmp ("count", type) == 0)
118 return (AGGR_TYPE_COUNT);
119 else if ((strcasecmp ("min", type) == 0)
120 || (strcasecmp ("minimum", type) == 0))
121 return (AGGR_TYPE_MIN);
122 else if ((strcasecmp ("max", type) == 0)
123 || (strcasecmp ("maximum", type) == 0))
124 return (AGGR_TYPE_MAX);
125 else if ((strcasecmp ("avg", type) == 0)
126 || (strcasecmp ("average", type) == 0))
127 return (AGGR_TYPE_AVG);
128 else if (strcasecmp ("sum", type) == 0)
129 return (AGGR_TYPE_SUM);
130 else if ((strcasecmp ("sdev", type) == 0)
131 || (strcasecmp ("stddev", type) == 0))
132 return (AGGR_TYPE_SDEV);
133 else
134 return (-1);
135 } /* }}} int parse_aggr_type */
137 static const char *aggr_type_to_string (int type) /* {{{ */
138 {
139 switch (type)
140 {
141 case AGGR_TYPE_COUNT: return ("Count");
142 case AGGR_TYPE_MIN: return ("Min");
143 case AGGR_TYPE_MAX: return ("Max");
144 case AGGR_TYPE_AVG: return ("Average");
145 case AGGR_TYPE_SUM: return ("Sum");
146 case AGGR_TYPE_SDEV: return ("Std. Dev.");
147 }
149 return ("UNKNOWN");
150 } /* }}} const char *aggr_type_to_string */
152 static int aggregation_type_add (const char *str_type) /* {{{ */
153 {
154 int type;
155 int *tmp;
156 size_t i;
158 type = parse_aggr_type (str_type);
159 if (type < 0)
160 {
161 fprintf (stderr, "ERROR: \"%s\" is not a known aggregation function.\n",
162 str_type);
163 return (type);
164 }
166 /* Check for duplicate definitions */
167 for (i = 0; i < aggregation_types_num; i++)
168 {
169 if (aggregation_types[i] == type)
170 {
171 fprintf (stderr, "ERROR: Multiple aggregations with type \"%s\" "
172 "defined.\n", str_type);
173 return (EEXIST);
174 }
175 }
177 tmp = realloc (aggregation_types,
178 (aggregation_types_num + 1) * sizeof (*aggregation_types));
179 if (tmp == NULL)
180 return (ENOMEM);
181 aggregation_types = tmp;
182 aggregation_types[aggregation_types_num] = type;
183 aggregation_types_num++;
185 return (0);
186 } /* }}} int aggregation_type_add */
188 static int group_name_from_ident (const lcc_identifier_t *identifier, /* {{{ */
189 char *buffer, size_t buffer_size)
190 {
191 if ((identifier == NULL)
192 || (buffer == NULL) || (buffer_size < 2))
193 return (EINVAL);
195 if (grouping == 0)
196 {
197 lcc_identifier_to_string (/* connection = */ NULL,
198 buffer, buffer_size, identifier);
199 buffer[buffer_size - 1] = 0;
200 return (0);
201 }
203 memset (buffer, 0, buffer_size);
205 #define COPY_FIELD(field,index) do { \
206 if ((grouping & (1 << index)) != 0) \
207 { \
208 if (buffer[0] == 0) \
209 strncpy (buffer, identifier->field, buffer_size); \
210 else \
211 { \
212 char tmp[buffer_size]; \
213 snprintf (tmp, buffer_size, "%s/%s", buffer, identifier->field); \
214 memcpy (buffer, tmp, buffer_size); \
215 } \
216 buffer[buffer_size - 1] = 0; \
217 } \
218 } while (0)
220 COPY_FIELD (host, 0);
221 COPY_FIELD (plugin, 1);
222 COPY_FIELD (plugin_instance, 2);
223 COPY_FIELD (type, 3);
224 COPY_FIELD (type_instance, 4);
226 #undef COPY_FIELD
228 return (0);
229 } /* }}} int group_name_from_ident */
231 static aggregation_group_t *aggregation_get_group ( const lcc_identifier_t *identifier) /* {{{ */
232 {
233 char group_name[LCC_NAME_LEN];
234 aggregation_group_t *g;
235 size_t i;
236 int status;
238 if (identifier == NULL)
239 return (NULL);
241 status = group_name_from_ident (identifier,
242 group_name, sizeof (group_name));
243 if (status != 0)
244 return (NULL);
246 for (i = 0; i < aggregation_groups_num; i++)
247 if (strcmp (group_name, aggregation_groups[i].name) == 0)
248 return (aggregation_groups + i);
250 g = realloc (aggregation_groups,
251 (aggregation_groups_num + 1) * sizeof (*aggregation_groups));
252 if (g == NULL)
253 return (NULL);
254 aggregation_groups = g;
255 g = aggregation_groups + aggregation_groups_num;
257 memset (g, 0, sizeof (*g));
258 g->name = strdup (group_name);
259 if (g->name == NULL)
260 return (NULL);
262 g->min = NAN;
263 g->max = NAN;
264 g->sum = NAN;
265 g->sum_of_squares = NAN;
267 aggregation_groups_num++;
268 return (g);
269 } /* }}} aggregation_group_t *aggregation_get_group */
271 static int aggregation_add_value (const lcc_identifier_t *identifier, /* {{{ */
272 double value)
273 {
274 aggregation_group_t *g;
276 if (identifier == NULL)
277 return (EINVAL);
279 g = aggregation_get_group (identifier);
280 if (g == NULL)
281 return (-1);
283 if (g->num == 0)
284 {
285 g->min = value;
286 g->max = value;
287 g->sum = value;
288 g->sum_of_squares = value * value;
289 g->num = 1;
290 return (0);
291 }
293 if (isnan (value))
294 return (0);
296 if (isnan (g->min) || (g->min > value))
297 g->min = value;
299 if (isnan (g->max) || (g->max < value))
300 g->max = value;
302 if (isnan (g->sum))
303 g->sum = value;
304 else
305 g->sum += value;
307 if (isnan (g->sum_of_squares))
308 g->sum_of_squares = value * value;
309 else
310 g->sum_of_squares += value * value;
312 g->num++;
314 return (0);
315 } /* }}} int aggregation_add_value */
317 static int read_data (lcc_connection_t *c) /* {{{ */
318 {
319 lcc_identifier_t *ret_ident = NULL;
320 size_t ret_ident_num = 0;
322 int status;
323 size_t i;
325 status = lcc_listval_with_selection (c,
326 re_host,
327 re_plugin,
328 re_plugin_instance,
329 re_type,
330 re_type_instance,
331 &ret_ident, &ret_ident_num);
332 if (status != 0)
333 {
334 fprintf (stderr, "ERROR: lcc_listval_with_selection: %s\n",
335 lcc_strerror (c));
336 return (-1);
337 }
338 assert ((ret_ident != NULL) || (ret_ident_num == 0));
340 /* Iterate over all returned identifiers and figure out which ones are
341 * interesting, i.e. match a selector in an aggregation. */
342 for (i = 0; i < ret_ident_num; ++i)
343 {
344 size_t ret_values_num = 0;
345 gauge_t *ret_values = NULL;
347 status = lcc_getval (c, ret_ident + i,
348 &ret_values_num, &ret_values, /* values_names = */ NULL);
349 if (status != 0)
350 {
351 fprintf (stderr, "ERROR: lcc_getval: %s\n", lcc_strerror (c));
352 continue;
353 }
354 assert (ret_values != NULL);
356 /* FIXME: What to do with multiple data sources values? */
357 aggregation_add_value (ret_ident + i, ret_values[0]);
359 free (ret_values);
360 } /* for (ret_ident) */
362 free (ret_ident);
364 return (0);
365 } /* }}} int read_data */
367 static int print_horizontal_line (int name_len_max) /* {{{ */
368 {
369 int i;
370 size_t j;
372 printf ("+-");
374 for (i = 0; i < name_len_max; i++)
375 printf ("-");
377 printf ("-+");
379 for (j = 0; j < aggregation_types_num; j++)
380 printf ("------------+");
381 if (aggregation_types_num == 0)
382 printf ("------------+");
384 printf ("\n");
386 return (0);
387 } /* }}} int print_horizontal_line */
389 static int write_data (void) /* {{{ */
390 {
391 int name_len_max = 4;
392 size_t i;
394 for (i = 0; i < aggregation_groups_num; i++)
395 {
396 int name_len = (int) strlen (aggregation_groups[i].name);
397 if (name_len_max < name_len)
398 name_len_max = name_len;
399 }
401 print_horizontal_line (name_len_max);
402 printf ("! %-*s !", name_len_max, "Name");
403 for (i = 0; i < aggregation_types_num; i++)
404 printf (" %10s !", aggr_type_to_string (aggregation_types[i]));
405 if (aggregation_types_num == 0)
406 printf (" %10s !", "Value");
407 printf ("\n");
408 print_horizontal_line (name_len_max);
410 for (i = 0; i < aggregation_groups_num; i++)
411 {
412 size_t j;
414 aggregation_group_t *g = aggregation_groups + i;
416 printf ("! %-*s !", name_len_max, g->name);
418 for (j = 0; j < aggregation_types_num; j++)
419 {
420 int type = aggregation_types[j];
421 double value = NAN;
423 if (type == AGGR_TYPE_COUNT)
424 value = (double) g->num;
425 else if (type == AGGR_TYPE_MIN)
426 value = g->min;
427 else if (type == AGGR_TYPE_MAX)
428 value = g->max;
429 else if (type == AGGR_TYPE_SUM)
430 value = g->sum;
431 else if ((type == AGGR_TYPE_AVG)
432 && (g->num > 0))
433 value = g->sum / ((double) g->num);
434 else if (type == AGGR_TYPE_SDEV)
435 {
436 if (g->num == 1)
437 value = 0.0;
438 else if (g->num > 1)
439 value = sqrt (
440 (
441 g->sum_of_squares
442 - ((g->sum * g->sum) / ((double) g->num))
443 )
444 / ((double) (g->num - 1)));
445 }
447 printf (" %10g !", value);
448 }
449 if (aggregation_types_num == 0)
450 {
451 /* g->num may be zero if the value is NAN. */
452 assert (g->num < 2);
453 printf (" %10g !", g->min);
454 }
456 printf ("\n");
457 }
459 print_horizontal_line (name_len_max);
461 return (0);
462 } /* }}} int write_data */
464 __attribute__((noreturn))
465 static void exit_usage (int status) /* {{{ */
466 {
467 printf ("Usage: collectdctl show [<Selection>] [<Aggregation> <Grouping>]\n"
468 "\n"
469 "Selection:\n"
470 "\n"
471 " host=<regex> Regex for the host name.\n"
472 " plugin=<regex> Regex for the plugin.\n"
473 " plugin_instance=<regex> Regex for the plugin instance.\n"
474 " type=<regex> Regex for the type.\n"
475 " type_instance=<regex> Regex for the type instance.\n"
476 "\n"
477 "Aggregation:\n"
478 "\n"
479 " aggregate=<aggr>[,<aggr>[...]] List of aggregations to use when\n"
480 " combining multiple values.\n"
481 " Valid aggregations are:\n"
482 " count, min, max, avg, sum, stddev\n"
483 "\n"
484 "Grouping:\n"
485 "\n"
486 " group=<field>[,<field>[...]] List of fields to group by.\n"
487 " Valid fields are:\n"
488 " host, plugin, plugin_instance,\n"
489 " type, type_instance\n"
490 "\n");
491 exit (status);
492 } /* }}} void exit_usage */
494 static int parse_aggregate (const char *aggr) /* {{{ */
495 {
496 char *aggr_copy;
497 char *dummy;
498 char *a;
500 aggr_copy = strdup (aggr);
501 if (aggr_copy == NULL)
502 return (ENOMEM);
504 free (aggregation_types);
505 aggregation_types = NULL;
506 aggregation_types_num = 0;
508 dummy = aggr_copy;
509 while ((a = strtok (dummy, ",")) != NULL)
510 {
511 int status;
513 dummy = NULL;
515 status = aggregation_type_add (a);
516 if (status != 0)
517 exit_usage (EXIT_FAILURE);
518 } /* while (strtok) */
520 free (aggr_copy);
522 return (0);
523 } /* }}} int parse_group */
525 static int parse_group (const char *group) /* {{{ */
526 {
527 char *group_copy;
528 char *dummy;
529 char *g;
531 group_copy = strdup (group);
532 if (group_copy == NULL)
533 return (ENOMEM);
535 grouping = 0;
537 dummy = group_copy;
538 while ((g = strtok (dummy, ",")) != NULL)
539 {
540 int pos = 0;
542 dummy = NULL;
544 if (strcasecmp ("host", g) == 0)
545 pos = 0;
546 else if (strcasecmp ("plugin", g) == 0)
547 pos = 1;
548 else if ((strcasecmp ("plugin_instance", g) == 0)
549 || (strcasecmp ("plugininstance", g) == 0)
550 || (strcasecmp ("pinst", g) == 0))
551 pos = 2;
552 else if (strcasecmp ("type", g) == 0)
553 pos = 3;
554 else if ((strcasecmp ("type_instance", g) == 0)
555 || (strcasecmp ("typeinstance", g) == 0)
556 || (strcasecmp ("tinst", g) == 0))
557 pos = 4;
558 else
559 {
560 fprintf (stderr, "Unknown grouping field: \"%s\"\n", g);
561 exit_usage (EXIT_FAILURE);
562 }
564 grouping |= 1 << pos;
565 } /* while (strtok) */
567 free (group_copy);
569 return (0);
570 } /* }}} int parse_group */
572 static int parse_arg (const char *arg) /* {{{ */
573 {
574 if (arg == NULL)
575 return (EINVAL);
576 else if (strncasecmp ("host=", arg, strlen ("host=")) == 0)
577 re_host = arg + strlen ("host=");
578 else if (strncasecmp ("plugin=", arg, strlen ("plugin=")) == 0)
579 re_plugin = arg + strlen ("plugin=");
580 else if (strncasecmp ("plugin_instance=", arg, strlen ("plugin_instance=")) == 0)
581 re_plugin_instance = arg + strlen ("plugin_instance=");
582 else if (strncasecmp ("type=", arg, strlen ("type=")) == 0)
583 re_type = arg + strlen ("type=");
584 else if (strncasecmp ("type_instance=", arg, strlen ("type_instance=")) == 0)
585 re_type_instance = arg + strlen ("type_instance=");
587 /* Grouping */
588 else if (strncasecmp ("group=", arg, strlen ("group=")) == 0)
589 return (parse_group (arg + strlen ("group=")));
591 /* Aggregations */
592 else if (strncasecmp ("aggregate=", arg, strlen ("aggregate=")) == 0)
593 return (parse_aggregate (arg + strlen ("aggregate=")));
595 /* Some alternative spellings to make it easier to guess a working argument
596 * name: */
597 else if (strncasecmp ("hostname=", arg, strlen ("hostname=")) == 0)
598 re_host = arg + strlen ("hostname=");
599 else if (strncasecmp ("plugininstance=", arg, strlen ("plugininstance=")) == 0)
600 re_plugin_instance = arg + strlen ("plugininstance=");
601 else if (strncasecmp ("typeinstance=", arg, strlen ("typeinstance=")) == 0)
602 re_type_instance = arg + strlen ("typeinstance=");
603 else if (strncasecmp ("pinst=", arg, strlen ("pinst=")) == 0)
604 re_plugin_instance = arg + strlen ("pinst=");
605 else if (strncasecmp ("tinst=", arg, strlen ("tinst=")) == 0)
606 re_type_instance = arg + strlen ("tinst=");
607 else if (strncasecmp ("aggr=", arg, strlen ("aggr=")) == 0)
608 return (parse_aggregate (arg + strlen ("aggr=")));
610 /* Don't know what that is ... */
611 else
612 {
613 fprintf (stderr, "Unknown argument: \"%s\"\n", arg);
614 exit_usage (EXIT_FAILURE);
615 }
617 return (0);
618 } /* }}} int parse_arg */
620 int show (lcc_connection_t *c, int argc, char **argv) /* {{{ */
621 {
622 int status;
623 int i;
624 size_t j;
626 for (i = 1; i < argc; i++)
627 {
628 status = parse_arg (argv[i]);
629 /* parse_arg calls exit_usage() on error. */
630 assert (status == 0);
631 }
633 if ((grouping == 0) && (aggregation_types_num > 0))
634 {
635 fprintf (stderr, "One or more aggregations were specified, but no fields "
636 "were selected for grouping values. Please use the ""\"group=...\" "
637 "option.\n");
638 exit_usage (EXIT_FAILURE);
639 }
640 else if ((grouping != 0) && (aggregation_types_num == 0))
641 {
642 fprintf (stderr, "One or more fields were specified for grouping but no "
643 "aggregation was given. Please use the \"aggregate=...\" option.\n");
644 exit_usage (EXIT_FAILURE);
645 }
647 status = read_data (c);
648 if (status != 0)
649 return (status);
651 status = write_data ();
652 if (status != 0)
653 return (status);
655 for (j = 0; j < aggregation_groups_num; j++)
656 free (aggregation_groups[j].name);
657 free (aggregation_groups);
658 free (aggregation_types);
660 return (0);
661 } /* }}} int show */
663 /* vim: set sw=2 ts=2 tw=78 expandtab fdm=marker : */