collectdctl: Base the "show" implementation on the new LISTVAL syntax.
[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  **/
21
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <strings.h>
31
32 #include <assert.h>
33 #include <errno.h>
34
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 */
65
66 #include "libcollectdclient/collectd/client.h"
67
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
74
75 /*
76  * Data structures
77  */
78 struct aggregation_group_s
79 {
80   char *name;
81
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;
89
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;
99
100 /* Grouping */
101 static uint16_t grouping = 0;
102
103 /* Aggregation */
104 static int *aggregation_types = NULL;
105 static size_t aggregation_types_num = 0;
106
107 static aggregation_group_t *aggregation_groups = NULL;
108 static size_t aggregation_groups_num = 0;
109
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 */
136
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   }
148
149   return ("UNKNOWN");
150 } /* }}} const char *aggr_type_to_string */
151
152 static int aggregation_type_add (const char *str_type) /* {{{ */
153 {
154   int type;
155   int *tmp;
156   size_t i;
157
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   }
165
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   }
176
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++;
184
185   return (0);
186 } /* }}} int aggregation_type_add */
187
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);
194
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   }
202
203   memset (buffer, 0, buffer_size);
204
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)
219
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);
225
226 #undef COPY_FIELD
227
228   return (0);
229 } /* }}} int group_name_from_ident */
230
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;
237
238   if (identifier == NULL)
239     return (NULL);
240
241   status = group_name_from_ident (identifier,
242       group_name, sizeof (group_name));
243   if (status != 0)
244     return (NULL);
245
246   for (i = 0; i < aggregation_groups_num; i++)
247     if (strcmp (group_name, aggregation_groups[i].name) == 0)
248       return (aggregation_groups + i);
249
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;
256
257   memset (g, 0, sizeof (*g));
258   g->name = strdup (group_name);
259   if (g->name == NULL)
260     return (NULL);
261
262   g->min = NAN;
263   g->max = NAN;
264   g->sum = NAN;
265   g->sum_of_squares = NAN;
266
267   aggregation_groups_num++;
268   return (g);
269 } /* }}} aggregation_group_t *aggregation_get_group */
270
271 static int aggregation_add_value (const lcc_identifier_t *identifier, /* {{{ */
272     double value)
273 {
274   aggregation_group_t *g;
275
276   if (identifier == NULL)
277     return (EINVAL);
278
279   g = aggregation_get_group (identifier);
280   if (g == NULL)
281     return (-1);
282
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   }
292
293   if (isnan (value))
294     return (0);
295
296   if (isnan (g->min) || (g->min > value))
297     g->min = value;
298
299   if (isnan (g->max) || (g->max < value))
300     g->max = value;
301
302   if (isnan (g->sum))
303     g->sum = value;
304   else
305     g->sum += value;
306
307   if (isnan (g->sum_of_squares))
308     g->sum_of_squares = value * value;
309   else
310     g->sum_of_squares += value * value;
311
312   g->num++;
313
314   return (0);
315 } /* }}} int aggregation_add_value */
316
317 static int read_data (lcc_connection_t *c) /* {{{ */
318 {
319   lcc_identifier_t *ret_ident     = NULL;
320   size_t            ret_ident_num = 0;
321
322   int status;
323   size_t i;
324
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));
339
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;
346
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);
355
356     /* FIXME: What to do with multiple data sources values? */
357     aggregation_add_value (ret_ident + i, ret_values[0]);
358
359     free (ret_values);
360   } /* for (ret_ident) */
361
362   free (ret_ident);
363
364   return (0);
365 } /* }}} int read_data */
366
367 static int print_horizontal_line (int name_len_max) /* {{{ */
368 {
369   int i;
370   size_t j;
371
372   printf ("+-");
373
374   for (i = 0; i < name_len_max; i++)
375     printf ("-");
376
377   printf ("-+");
378
379   for (j = 0; j < aggregation_types_num; j++)
380     printf ("------------+");
381   if (aggregation_types_num == 0)
382     printf ("------------+");
383
384   printf ("\n");
385
386   return (0);
387 } /* }}} int print_horizontal_line */
388
389 static int write_data (void) /* {{{ */
390 {
391   int name_len_max = 4;
392   size_t i;
393
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   }
400
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);
409
410   for (i = 0; i < aggregation_groups_num; i++)
411   {
412     size_t j;
413
414     aggregation_group_t *g = aggregation_groups + i;
415
416     printf ("! %-*s !", name_len_max, g->name);
417
418     for (j = 0; j < aggregation_types_num; j++)
419     {
420       int type = aggregation_types[j];
421       double value = NAN;
422
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       }
446
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     }
455
456     printf ("\n");
457   }
458
459   print_horizontal_line (name_len_max);
460
461   return (0);
462 } /* }}} int write_data */
463
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 */
493
494 static int parse_aggregate (const char *aggr) /* {{{ */
495 {
496   char *aggr_copy;
497   char *dummy;
498   char *a;
499
500   aggr_copy = strdup (aggr);
501   if (aggr_copy == NULL)
502     return (ENOMEM);
503
504   free (aggregation_types);
505   aggregation_types = NULL;
506   aggregation_types_num = 0;
507
508   dummy = aggr_copy;
509   while ((a = strtok (dummy, ",")) != NULL)
510   {
511     int status;
512
513     dummy = NULL;
514
515     status = aggregation_type_add (a);
516     if (status != 0)
517       exit_usage (EXIT_FAILURE);
518   } /* while (strtok) */
519
520   free (aggr_copy);
521
522   return (0);
523 } /* }}} int parse_group */
524
525 static int parse_group (const char *group) /* {{{ */
526 {
527   char *group_copy;
528   char *dummy;
529   char *g;
530
531   group_copy = strdup (group);
532   if (group_copy == NULL)
533     return (ENOMEM);
534
535   grouping = 0;
536
537   dummy = group_copy;
538   while ((g = strtok (dummy, ",")) != NULL)
539   {
540     int pos = 0;
541
542     dummy = NULL;
543
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     }
563
564     grouping |= 1 << pos;
565   } /* while (strtok) */
566
567   free (group_copy);
568
569   return (0);
570 } /* }}} int parse_group */
571
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=");
586
587   /* Grouping */
588   else if (strncasecmp ("group=", arg, strlen ("group=")) == 0)
589     return (parse_group (arg + strlen ("group=")));
590
591   /* Aggregations */
592   else if (strncasecmp ("aggregate=", arg, strlen ("aggregate=")) == 0)
593     return (parse_aggregate (arg + strlen ("aggregate=")));
594
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=")));
609
610   /* Don't know what that is ... */
611   else
612   {
613     fprintf (stderr, "Unknown argument: \"%s\"\n", arg);
614     exit_usage (EXIT_FAILURE);
615   }
616
617   return (0);
618 } /* }}} int parse_arg */
619
620 int show (lcc_connection_t *c, int argc, char **argv) /* {{{ */
621 {
622   int status;
623   int i;
624   size_t j;
625
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   }
632
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   }
646
647   status = read_data (c);
648   if (status != 0)
649     return (status);
650
651   status = write_data ();
652   if (status != 0)
653     return (status);
654
655   for (j = 0; j < aggregation_groups_num; j++)
656     free (aggregation_groups[j].name);
657   free (aggregation_groups);
658   free (aggregation_types);
659
660   return (0);
661 } /* }}} int show */
662
663 /* vim: set sw=2 ts=2 tw=78 expandtab fdm=marker : */