Code

collectdctl: Add the "show" command.
[collectd.git] / src / collectdctl-show.c
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) /* {{{ */
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) /* {{{ */
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) /* {{{ */
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)
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)
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) /* {{{ */
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)
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) /* {{{ */
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) /* {{{ */
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) /* {{{ */
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) /* {{{ */
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) /* {{{ */
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 : */