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 static lcc_identifier_t *selector;
95 static int *aggregation_types = NULL;
96 static size_t aggregation_types_num = 0;
98 static aggregation_group_t *aggregation_groups = NULL;
99 static size_t aggregation_groups_num = 0;
101 /*
102 * Private functions
103 */
104 static int parse_aggr_type (const char *type) /* {{{ */
105 {
106 if (type == NULL)
107 return (-1);
108 else if (strcasecmp ("count", type) == 0)
109 return (AGGR_TYPE_COUNT);
110 else if ((strcasecmp ("min", type) == 0)
111 || (strcasecmp ("minimum", type) == 0))
112 return (AGGR_TYPE_MIN);
113 else if ((strcasecmp ("max", type) == 0)
114 || (strcasecmp ("maximum", type) == 0))
115 return (AGGR_TYPE_MAX);
116 else if ((strcasecmp ("avg", type) == 0)
117 || (strcasecmp ("average", type) == 0))
118 return (AGGR_TYPE_AVG);
119 else if (strcasecmp ("sum", type) == 0)
120 return (AGGR_TYPE_SUM);
121 else if ((strcasecmp ("sdev", type) == 0)
122 || (strcasecmp ("stddev", type) == 0))
123 return (AGGR_TYPE_SDEV);
124 else
125 return (-1);
126 } /* }}} int parse_aggr_type */
128 static const char *aggr_type_to_string (int type) /* {{{ */
129 {
130 switch (type)
131 {
132 case AGGR_TYPE_COUNT: return ("Count");
133 case AGGR_TYPE_MIN: return ("Min");
134 case AGGR_TYPE_MAX: return ("Max");
135 case AGGR_TYPE_AVG: return ("Average");
136 case AGGR_TYPE_SUM: return ("Sum");
137 case AGGR_TYPE_SDEV: return ("Std. Dev.");
138 }
140 return ("UNKNOWN");
141 } /* }}} const char *aggr_type_to_string */
143 static int aggregation_type_add (const char *str_type) /* {{{ */
144 {
145 int type;
146 int *tmp;
147 size_t i;
149 type = parse_aggr_type (str_type);
150 if (type < 0)
151 {
152 fprintf (stderr, "ERROR: \"%s\" is not a known aggregation function.\n",
153 str_type);
154 return (type);
155 }
157 /* Check for duplicate definitions */
158 for (i = 0; i < aggregation_types_num; i++)
159 {
160 if (aggregation_types[i] == type)
161 {
162 fprintf (stderr, "ERROR: Multiple aggregations with type \"%s\" "
163 "defined.\n", str_type);
164 return (EEXIST);
165 }
166 }
168 tmp = realloc (aggregation_types,
169 (aggregation_types_num + 1) * sizeof (*aggregation_types));
170 if (tmp == NULL)
171 return (ENOMEM);
172 aggregation_types = tmp;
173 aggregation_types[aggregation_types_num] = type;
174 aggregation_types_num++;
176 return (0);
177 } /* }}} int aggregation_type_add */
179 static int group_name_from_ident (const lcc_identifier_t *selector, /* {{{ */
180 const lcc_identifier_t *identifier,
181 char *buffer, size_t buffer_size)
182 {
183 if ((selector == NULL)
184 || (identifier == NULL)
185 || (buffer == NULL) || (buffer_size < 2))
186 return (EINVAL);
188 /* Check if there is no "grouping" wildcard. If there isn't, return "all" as
189 * the default value. */
190 if ((strcmp ("+", selector->host) != 0)
191 && (strcmp ("+", selector->plugin) != 0)
192 && (strcmp ("+", selector->plugin_instance) != 0)
193 && (strcmp ("+", selector->type) != 0)
194 && (strcmp ("+", selector->type_instance) != 0))
195 {
196 /* There is no wildcard at all => use the identifier. */
197 if ((strcmp ("*", selector->host) != 0)
198 && (strcmp ("*", selector->plugin) != 0)
199 && (strcmp ("*", selector->plugin_instance) != 0)
200 && (strcmp ("*", selector->type) != 0)
201 && (strcmp ("*", selector->type_instance) != 0))
202 lcc_identifier_to_string (/* connection = */ NULL,
203 buffer, buffer_size, identifier);
204 else /* there's wildcards but no grouping */
205 strncpy (buffer, "all", buffer_size);
206 buffer[buffer_size - 1] = 0;
207 return (0);
208 }
210 memset (buffer, 0, buffer_size);
212 #define COPY_FIELD(field) do { \
213 if (strcmp ("+", selector->field) != 0) \
214 break; \
215 if (buffer[0] == 0) \
216 strncpy (buffer, identifier->field, buffer_size); \
217 else \
218 { \
219 char tmp[buffer_size]; \
220 snprintf (tmp, buffer_size, "%s/%s", buffer, identifier->field); \
221 memcpy (buffer, tmp, buffer_size); \
222 } \
223 buffer[buffer_size - 1] = 0; \
224 } while (0)
226 COPY_FIELD (host);
227 COPY_FIELD (plugin);
228 COPY_FIELD (plugin_instance);
229 COPY_FIELD (type);
230 COPY_FIELD (type_instance);
232 #undef COPY_FIELD
234 return (0);
235 } /* }}} int group_name_from_ident */
237 static _Bool ident_matches_selector (const lcc_identifier_t *selector, /* {{{ */
238 const lcc_identifier_t *identifier)
239 {
240 if ((selector == NULL) || (identifier == NULL))
241 return (0);
243 if ((strcmp (identifier->host, selector->host) != 0)
244 && (strcmp ("*", selector->host) != 0)
245 && (strcmp ("+", selector->host) != 0))
246 return (0);
248 if ((strcmp (identifier->plugin, selector->plugin) != 0)
249 && (strcmp ("*", selector->plugin) != 0)
250 && (strcmp ("+", selector->plugin) != 0))
251 return (0);
253 if ((strcmp (identifier->plugin_instance, selector->plugin_instance) != 0)
254 && (strcmp ("*", selector->plugin_instance) != 0)
255 && (strcmp ("+", selector->plugin_instance) != 0))
256 return (0);
258 if ((strcmp (identifier->type, selector->type) != 0)
259 && (strcmp ("*", selector->type) != 0)
260 && (strcmp ("+", selector->type) != 0))
261 return (0);
263 if ((strcmp (identifier->type_instance, selector->type_instance) != 0)
264 && (strcmp ("*", selector->type_instance) != 0)
265 && (strcmp ("+", selector->type_instance) != 0))
266 return (0);
268 return (1);
269 } /* }}} _Bool ident_matches_selector */
271 static aggregation_group_t *aggregation_get_group ( const lcc_identifier_t *identifier) /* {{{ */
272 {
273 char group_name[LCC_NAME_LEN];
274 aggregation_group_t *g;
275 size_t i;
276 int status;
278 if (identifier == NULL)
279 return (NULL);
281 status = group_name_from_ident (selector, identifier,
282 group_name, sizeof (group_name));
283 if (status != 0)
284 return (NULL);
286 for (i = 0; i < aggregation_groups_num; i++)
287 if (strcmp (group_name, aggregation_groups[i].name) == 0)
288 return (aggregation_groups + i);
290 g = realloc (aggregation_groups,
291 (aggregation_groups_num + 1) * sizeof (*aggregation_groups));
292 if (g == NULL)
293 return (NULL);
294 aggregation_groups = g;
295 g = aggregation_groups + aggregation_groups_num;
297 memset (g, 0, sizeof (*g));
298 g->name = strdup (group_name);
299 if (g->name == NULL)
300 return (NULL);
302 g->min = NAN;
303 g->max = NAN;
304 g->sum = NAN;
305 g->sum_of_squares = NAN;
307 aggregation_groups_num++;
308 return (g);
309 } /* }}} aggregation_group_t *aggregation_get_group */
311 static int aggregation_add_value (const lcc_identifier_t *identifier, /* {{{ */
312 double value)
313 {
314 aggregation_group_t *g;
316 if (identifier == NULL)
317 return (EINVAL);
319 g = aggregation_get_group (identifier);
320 if (g == NULL)
321 return (-1);
323 if (g->num == 0)
324 {
325 g->min = value;
326 g->max = value;
327 g->sum = value;
328 g->sum_of_squares = value * value;
329 g->num = 1;
330 return (0);
331 }
333 if (isnan (value))
334 return (0);
336 if (isnan (g->min) || (g->min > value))
337 g->min = value;
339 if (isnan (g->max) || (g->max < value))
340 g->max = value;
342 if (isnan (g->sum))
343 g->sum = value;
344 else
345 g->sum += value;
347 if (isnan (g->sum_of_squares))
348 g->sum_of_squares = value * value;
349 else
350 g->sum_of_squares += value * value;
352 g->num++;
354 return (0);
355 } /* }}} int aggregation_add_value */
357 static int read_data (lcc_connection_t *c) /* {{{ */
358 {
359 lcc_identifier_t *ret_ident = NULL;
360 size_t ret_ident_num = 0;
362 int status;
363 size_t i;
365 status = lcc_listval (c, &ret_ident, &ret_ident_num);
366 if (status != 0)
367 {
368 fprintf (stderr, "ERROR: lcc_listval: %s\n", lcc_strerror (c));
369 return (-1);
370 }
371 assert ((ret_ident != NULL) || (ret_ident_num == 0));
373 /* Iterate over all returned identifiers and figure out which ones are
374 * interesting, i.e. match a selector in an aggregation. */
375 for (i = 0; i < ret_ident_num; ++i)
376 {
377 size_t ret_values_num = 0;
378 gauge_t *ret_values = NULL;
380 if (!ident_matches_selector (selector, ret_ident + i))
381 continue;
383 status = lcc_getval (c, ret_ident + i,
384 &ret_values_num, &ret_values, /* values_names = */ NULL);
385 if (status != 0)
386 {
387 fprintf (stderr, "ERROR: lcc_getval: %s\n", lcc_strerror (c));
388 continue;
389 }
390 assert (ret_values != NULL);
392 /* FIXME: What to do with multiple data sources values? */
393 aggregation_add_value (ret_ident + i, ret_values[0]);
395 free (ret_values);
396 } /* for (ret_ident) */
398 free (ret_ident);
400 return (0);
401 } /* }}} int read_data */
403 static int print_horizontal_line (int name_len_max) /* {{{ */
404 {
405 int i;
406 size_t j;
408 printf ("+-");
410 for (i = 0; i < name_len_max; i++)
411 printf ("-");
413 printf ("-+");
415 for (j = 0; j < aggregation_types_num; j++)
416 printf ("------------+");
418 printf ("\n");
420 return (0);
421 } /* }}} int print_horizontal_line */
423 static int write_data (void) /* {{{ */
424 {
425 int name_len_max = 4;
426 size_t i;
428 for (i = 0; i < aggregation_groups_num; i++)
429 {
430 int name_len = (int) strlen (aggregation_groups[i].name);
431 if (name_len_max < name_len)
432 name_len_max = name_len;
433 }
435 print_horizontal_line (name_len_max);
436 printf ("! %-*s !", name_len_max, "Name");
437 for (i = 0; i < aggregation_types_num; i++)
438 printf (" %10s !", aggr_type_to_string (aggregation_types[i]));
439 printf ("\n");
440 print_horizontal_line (name_len_max);
442 for (i = 0; i < aggregation_groups_num; i++)
443 {
444 size_t j;
446 aggregation_group_t *g = aggregation_groups + i;
448 printf ("! %-*s !", name_len_max, g->name);
450 for (j = 0; j < aggregation_types_num; j++)
451 {
452 int type = aggregation_types[j];
453 double value = NAN;
455 if (type == AGGR_TYPE_COUNT)
456 value = (double) g->num;
457 else if (type == AGGR_TYPE_MIN)
458 value = g->min;
459 else if (type == AGGR_TYPE_MAX)
460 value = g->max;
461 else if (type == AGGR_TYPE_SUM)
462 value = g->sum;
463 else if ((type == AGGR_TYPE_AVG)
464 && (g->num > 0))
465 value = g->sum / ((double) g->num);
466 else if (type == AGGR_TYPE_SDEV)
467 {
468 if (g->num == 1)
469 value = 0.0;
470 else if (g->num > 1)
471 value = sqrt (
472 (
473 g->sum_of_squares
474 - ((g->sum * g->sum) / ((double) g->num))
475 )
476 / ((double) (g->num - 1)));
477 }
479 printf (" %10g !", value);
480 }
482 printf ("\n");
483 }
485 print_horizontal_line (name_len_max);
487 return (0);
488 } /* }}} int write_data */
490 __attribute__((noreturn))
491 static void exit_usage (int status) /* {{{ */
492 {
493 printf ("Usage: collectdctl show <selector> <aggregation> "
494 "[<aggregation> ...]\n"
495 "\n"
496 "Selector:\n"
497 " A selector is an identifier, where each part may be replaced "
498 "with either\n"
499 " \"*\" or \"+\".\n"
500 "\n"
501 "Aggregation:\n"
502 " count\n"
503 " min\n"
504 " max\n"
505 " avg\n"
506 "\n");
507 exit (status);
508 } /* }}} void exit_usage */
510 int show (lcc_connection_t *c, int argc, char **argv) /* {{{ */
511 {
512 lcc_identifier_t tmp;
513 int status;
514 int i;
515 size_t j;
517 if (argc < 3)
518 exit_usage (EXIT_FAILURE);
520 memset (&tmp, 0, sizeof (tmp));
521 status = lcc_string_to_identifier (c, &tmp, argv[1]);
522 if (status != 0)
523 return (status);
524 selector = &tmp;
526 for (i = 2; i < argc; i++)
527 aggregation_type_add (argv[i]);
529 status = read_data (c);
530 if (status != 0)
531 return (status);
533 status = write_data ();
534 if (status != 0)
535 return (status);
537 for (j = 0; j < aggregation_groups_num; j++)
538 free (aggregation_groups[j].name);
539 free (aggregation_groups);
540 free (aggregation_types);
542 return (0);
543 } /* }}} int show */
545 /* vim: set sw=2 ts=2 tw=78 expandtab fdm=marker : */