summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 70e16e5)
raw | patch | inline | side by side (parent: 70e16e5)
author | Florian Forster <octo@huhu.verplant.org> | |
Mon, 14 Mar 2011 09:41:17 +0000 (10:41 +0100) | ||
committer | Florian Forster <octo@huhu.verplant.org> | |
Mon, 14 Mar 2011 16:57:04 +0000 (17:57 +0100) |
The show command can be used to do SQL-like queries against the current
values stored within the daemon.
values stored within the daemon.
src/Makefile.am | patch | blob | history | |
src/collectdctl-show.c | [new file with mode: 0644] | patch | blob |
src/collectdctl.c | patch | blob | history | |
src/collectdctl.pod | patch | blob | history |
diff --git a/src/Makefile.am b/src/Makefile.am
index 5728144055b3dc1b3d026360bf65400332ddcf89..89efb509a6cfbd53a8ef238bfc4256e02a922389 100644 (file)
--- a/src/Makefile.am
+++ b/src/Makefile.am
collectd_nagios_DEPENDENCIES = libcollectdclient/libcollectdclient.la
-collectdctl_SOURCES = collectdctl.c
-collectdctl_LDADD =
+collectdctl_SOURCES = collectdctl.c collectdctl-show.c
+collectdctl_LDADD = -lm
if BUILD_WITH_LIBSOCKET
collectdctl_LDADD += -lsocket
endif
-if BUILD_AIX
-collectdctl_LDADD += -lm
-endif
collectdctl_LDADD += libcollectdclient/libcollectdclient.la
collectdctl_DEPENDENCIES = libcollectdclient/libcollectdclient.la
diff --git a/src/collectdctl-show.c b/src/collectdctl-show.c
--- /dev/null
+++ b/src/collectdctl-show.c
@@ -0,0 +1,545 @@
+/**
+ * collectd - src/collectdctl-show.c
+ * Copyright (C) 2011 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Florian "octo" Forster <octo at collectd.org>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#include <assert.h>
+#include <errno.h>
+
+#if NAN_STATIC_DEFAULT
+# include <math.h>
+/* #endif NAN_STATIC_DEFAULT*/
+#elif NAN_STATIC_ISOC
+# ifndef __USE_ISOC99
+# define DISABLE_ISOC99 1
+# define __USE_ISOC99 1
+# endif /* !defined(__USE_ISOC99) */
+# include <math.h>
+# if DISABLE_ISOC99
+# undef DISABLE_ISOC99
+# undef __USE_ISOC99
+# endif /* DISABLE_ISOC99 */
+/* #endif NAN_STATIC_ISOC */
+#elif NAN_ZERO_ZERO
+# include <math.h>
+# ifdef NAN
+# undef NAN
+# endif
+# define NAN (0.0 / 0.0)
+# ifndef isnan
+# define isnan(f) ((f) != (f))
+# endif /* !defined(isnan) */
+# ifndef isfinite
+# define isfinite(f) (((f) - (f)) == 0.0)
+# endif
+# ifndef isinf
+# define isinf(f) (!isfinite(f) && !isnan(f))
+# endif
+#endif /* NAN_ZERO_ZERO */
+
+#include "libcollectdclient/client.h"
+
+#define AGGR_TYPE_COUNT 0
+#define AGGR_TYPE_MIN 1
+#define AGGR_TYPE_MAX 2
+#define AGGR_TYPE_AVG 3
+#define AGGR_TYPE_SUM 4
+#define AGGR_TYPE_SDEV 5
+
+/*
+ * Data structures
+ */
+struct aggregation_group_s
+{
+ char *name;
+
+ int num;
+ double min;
+ double max;
+ double sum;
+ double sum_of_squares;
+};
+typedef struct aggregation_group_s aggregation_group_t;
+
+/*
+ * Global variables
+ */
+static lcc_identifier_t *selector;
+
+static int *aggregation_types = NULL;
+static size_t aggregation_types_num = 0;
+
+static aggregation_group_t *aggregation_groups = NULL;
+static size_t aggregation_groups_num = 0;
+
+/*
+ * Private functions
+ */
+static int parse_aggr_type (const char *type) /* {{{ */
+{
+ if (type == NULL)
+ return (-1);
+ else if (strcasecmp ("count", type) == 0)
+ return (AGGR_TYPE_COUNT);
+ else if ((strcasecmp ("min", type) == 0)
+ || (strcasecmp ("minimum", type) == 0))
+ return (AGGR_TYPE_MIN);
+ else if ((strcasecmp ("max", type) == 0)
+ || (strcasecmp ("maximum", type) == 0))
+ return (AGGR_TYPE_MAX);
+ else if ((strcasecmp ("avg", type) == 0)
+ || (strcasecmp ("average", type) == 0))
+ return (AGGR_TYPE_AVG);
+ else if (strcasecmp ("sum", type) == 0)
+ return (AGGR_TYPE_SUM);
+ else if ((strcasecmp ("sdev", type) == 0)
+ || (strcasecmp ("stddev", type) == 0))
+ return (AGGR_TYPE_SDEV);
+ else
+ return (-1);
+} /* }}} int parse_aggr_type */
+
+static const char *aggr_type_to_string (int type) /* {{{ */
+{
+ switch (type)
+ {
+ case AGGR_TYPE_COUNT: return ("Count");
+ case AGGR_TYPE_MIN: return ("Min");
+ case AGGR_TYPE_MAX: return ("Max");
+ case AGGR_TYPE_AVG: return ("Average");
+ case AGGR_TYPE_SUM: return ("Sum");
+ case AGGR_TYPE_SDEV: return ("Std. Dev.");
+ }
+
+ return ("UNKNOWN");
+} /* }}} const char *aggr_type_to_string */
+
+static int aggregation_type_add (const char *str_type) /* {{{ */
+{
+ int type;
+ int *tmp;
+ size_t i;
+
+ type = parse_aggr_type (str_type);
+ if (type < 0)
+ {
+ fprintf (stderr, "ERROR: \"%s\" is not a known aggregation function.\n",
+ str_type);
+ return (type);
+ }
+
+ /* Check for duplicate definitions */
+ for (i = 0; i < aggregation_types_num; i++)
+ {
+ if (aggregation_types[i] == type)
+ {
+ fprintf (stderr, "ERROR: Multiple aggregations with type \"%s\" "
+ "defined.\n", str_type);
+ return (EEXIST);
+ }
+ }
+
+ tmp = realloc (aggregation_types,
+ (aggregation_types_num + 1) * sizeof (*aggregation_types));
+ if (tmp == NULL)
+ return (ENOMEM);
+ aggregation_types = tmp;
+ aggregation_types[aggregation_types_num] = type;
+ aggregation_types_num++;
+
+ return (0);
+} /* }}} int aggregation_type_add */
+
+static int group_name_from_ident (const lcc_identifier_t *selector, /* {{{ */
+ const lcc_identifier_t *identifier,
+ char *buffer, size_t buffer_size)
+{
+ if ((selector == NULL)
+ || (identifier == NULL)
+ || (buffer == NULL) || (buffer_size < 2))
+ return (EINVAL);
+
+ /* Check if there is no "grouping" wildcard. If there isn't, return "all" as
+ * the default value. */
+ if ((strcmp ("+", selector->host) != 0)
+ && (strcmp ("+", selector->plugin) != 0)
+ && (strcmp ("+", selector->plugin_instance) != 0)
+ && (strcmp ("+", selector->type) != 0)
+ && (strcmp ("+", selector->type_instance) != 0))
+ {
+ /* There is no wildcard at all => use the identifier. */
+ if ((strcmp ("*", selector->host) != 0)
+ && (strcmp ("*", selector->plugin) != 0)
+ && (strcmp ("*", selector->plugin_instance) != 0)
+ && (strcmp ("*", selector->type) != 0)
+ && (strcmp ("*", selector->type_instance) != 0))
+ lcc_identifier_to_string (/* connection = */ NULL,
+ buffer, buffer_size, identifier);
+ else /* there's wildcards but no grouping */
+ strncpy (buffer, "all", buffer_size);
+ buffer[buffer_size - 1] = 0;
+ return (0);
+ }
+
+ memset (buffer, 0, buffer_size);
+
+#define COPY_FIELD(field) do { \
+ if (strcmp ("+", selector->field) != 0) \
+ break; \
+ if (buffer[0] == 0) \
+ strncpy (buffer, identifier->field, buffer_size); \
+ else \
+ { \
+ char tmp[buffer_size]; \
+ snprintf (tmp, buffer_size, "%s/%s", buffer, identifier->field); \
+ memcpy (buffer, tmp, buffer_size); \
+ } \
+ buffer[buffer_size - 1] = 0; \
+} while (0)
+
+ COPY_FIELD (host);
+ COPY_FIELD (plugin);
+ COPY_FIELD (plugin_instance);
+ COPY_FIELD (type);
+ COPY_FIELD (type_instance);
+
+#undef COPY_FIELD
+
+ return (0);
+} /* }}} int group_name_from_ident */
+
+static _Bool ident_matches_selector (const lcc_identifier_t *selector, /* {{{ */
+ const lcc_identifier_t *identifier)
+{
+ if ((selector == NULL) || (identifier == NULL))
+ return (0);
+
+ if ((strcmp (identifier->host, selector->host) != 0)
+ && (strcmp ("*", selector->host) != 0)
+ && (strcmp ("+", selector->host) != 0))
+ return (0);
+
+ if ((strcmp (identifier->plugin, selector->plugin) != 0)
+ && (strcmp ("*", selector->plugin) != 0)
+ && (strcmp ("+", selector->plugin) != 0))
+ return (0);
+
+ if ((strcmp (identifier->plugin_instance, selector->plugin_instance) != 0)
+ && (strcmp ("*", selector->plugin_instance) != 0)
+ && (strcmp ("+", selector->plugin_instance) != 0))
+ return (0);
+
+ if ((strcmp (identifier->type, selector->type) != 0)
+ && (strcmp ("*", selector->type) != 0)
+ && (strcmp ("+", selector->type) != 0))
+ return (0);
+
+ if ((strcmp (identifier->type_instance, selector->type_instance) != 0)
+ && (strcmp ("*", selector->type_instance) != 0)
+ && (strcmp ("+", selector->type_instance) != 0))
+ return (0);
+
+ return (1);
+} /* }}} _Bool ident_matches_selector */
+
+static aggregation_group_t *aggregation_get_group ( const lcc_identifier_t *identifier) /* {{{ */
+{
+ char group_name[LCC_NAME_LEN];
+ aggregation_group_t *g;
+ size_t i;
+ int status;
+
+ if (identifier == NULL)
+ return (NULL);
+
+ status = group_name_from_ident (selector, identifier,
+ group_name, sizeof (group_name));
+ if (status != 0)
+ return (NULL);
+
+ for (i = 0; i < aggregation_groups_num; i++)
+ if (strcmp (group_name, aggregation_groups[i].name) == 0)
+ return (aggregation_groups + i);
+
+ g = realloc (aggregation_groups,
+ (aggregation_groups_num + 1) * sizeof (*aggregation_groups));
+ if (g == NULL)
+ return (NULL);
+ aggregation_groups = g;
+ g = aggregation_groups + aggregation_groups_num;
+
+ memset (g, 0, sizeof (*g));
+ g->name = strdup (group_name);
+ if (g->name == NULL)
+ return (NULL);
+
+ g->min = NAN;
+ g->max = NAN;
+ g->sum = NAN;
+ g->sum_of_squares = NAN;
+
+ aggregation_groups_num++;
+ return (g);
+} /* }}} aggregation_group_t *aggregation_get_group */
+
+static int aggregation_add_value (const lcc_identifier_t *identifier, /* {{{ */
+ double value)
+{
+ aggregation_group_t *g;
+
+ if (identifier == NULL)
+ return (EINVAL);
+
+ g = aggregation_get_group (identifier);
+ if (g == NULL)
+ return (-1);
+
+ if (g->num == 0)
+ {
+ g->min = value;
+ g->max = value;
+ g->sum = value;
+ g->sum_of_squares = value * value;
+ g->num = 1;
+ return (0);
+ }
+
+ if (isnan (value))
+ return (0);
+
+ if (isnan (g->min) || (g->min > value))
+ g->min = value;
+
+ if (isnan (g->max) || (g->max < value))
+ g->max = value;
+
+ if (isnan (g->sum))
+ g->sum = value;
+ else
+ g->sum += value;
+
+ if (isnan (g->sum_of_squares))
+ g->sum_of_squares = value * value;
+ else
+ g->sum_of_squares += value * value;
+
+ g->num++;
+
+ return (0);
+} /* }}} int aggregation_add_value */
+
+static int read_data (lcc_connection_t *c) /* {{{ */
+{
+ lcc_identifier_t *ret_ident = NULL;
+ size_t ret_ident_num = 0;
+
+ int status;
+ size_t i;
+
+ status = lcc_listval (c, &ret_ident, &ret_ident_num);
+ if (status != 0)
+ {
+ fprintf (stderr, "ERROR: lcc_listval: %s\n", lcc_strerror (c));
+ return (-1);
+ }
+ assert ((ret_ident != NULL) || (ret_ident_num == 0));
+
+ /* Iterate over all returned identifiers and figure out which ones are
+ * interesting, i.e. match a selector in an aggregation. */
+ for (i = 0; i < ret_ident_num; ++i)
+ {
+ size_t ret_values_num = 0;
+ gauge_t *ret_values = NULL;
+
+ if (!ident_matches_selector (selector, ret_ident + i))
+ continue;
+
+ status = lcc_getval (c, ret_ident + i,
+ &ret_values_num, &ret_values, /* values_names = */ NULL);
+ if (status != 0)
+ {
+ fprintf (stderr, "ERROR: lcc_getval: %s\n", lcc_strerror (c));
+ continue;
+ }
+ assert (ret_values != NULL);
+
+ /* FIXME: What to do with multiple data sources values? */
+ aggregation_add_value (ret_ident + i, ret_values[0]);
+
+ free (ret_values);
+ } /* for (ret_ident) */
+
+ free (ret_ident);
+
+ return (0);
+} /* }}} int read_data */
+
+static int print_horizontal_line (int name_len_max) /* {{{ */
+{
+ int i;
+ size_t j;
+
+ printf ("+-");
+
+ for (i = 0; i < name_len_max; i++)
+ printf ("-");
+
+ printf ("-+");
+
+ for (j = 0; j < aggregation_types_num; j++)
+ printf ("------------+");
+
+ printf ("\n");
+
+ return (0);
+} /* }}} int print_horizontal_line */
+
+static int write_data (void) /* {{{ */
+{
+ int name_len_max = 4;
+ size_t i;
+
+ for (i = 0; i < aggregation_groups_num; i++)
+ {
+ int name_len = (int) strlen (aggregation_groups[i].name);
+ if (name_len_max < name_len)
+ name_len_max = name_len;
+ }
+
+ print_horizontal_line (name_len_max);
+ printf ("! %-*s !", name_len_max, "Name");
+ for (i = 0; i < aggregation_types_num; i++)
+ printf (" %10s !", aggr_type_to_string (aggregation_types[i]));
+ printf ("\n");
+ print_horizontal_line (name_len_max);
+
+ for (i = 0; i < aggregation_groups_num; i++)
+ {
+ size_t j;
+
+ aggregation_group_t *g = aggregation_groups + i;
+
+ printf ("! %-*s !", name_len_max, g->name);
+
+ for (j = 0; j < aggregation_types_num; j++)
+ {
+ int type = aggregation_types[j];
+ double value = NAN;
+
+ if (type == AGGR_TYPE_COUNT)
+ value = (double) g->num;
+ else if (type == AGGR_TYPE_MIN)
+ value = g->min;
+ else if (type == AGGR_TYPE_MAX)
+ value = g->max;
+ else if (type == AGGR_TYPE_SUM)
+ value = g->sum;
+ else if ((type == AGGR_TYPE_AVG)
+ && (g->num > 0))
+ value = g->sum / ((double) g->num);
+ else if (type == AGGR_TYPE_SDEV)
+ {
+ if (g->num == 1)
+ value = 0.0;
+ else if (g->num > 1)
+ value = sqrt (
+ (
+ g->sum_of_squares
+ - ((g->sum * g->sum) / ((double) g->num))
+ )
+ / ((double) (g->num - 1)));
+ }
+
+ printf (" %10g !", value);
+ }
+
+ printf ("\n");
+ }
+
+ print_horizontal_line (name_len_max);
+
+ return (0);
+} /* }}} int write_data */
+
+__attribute__((noreturn))
+static void exit_usage (int status) /* {{{ */
+{
+ printf ("Usage: collectdctl show <selector> <aggregation> "
+ "[<aggregation> ...]\n"
+ "\n"
+ "Selector:\n"
+ " A selector is an identifier, where each part may be replaced "
+ "with either\n"
+ " \"*\" or \"+\".\n"
+ "\n"
+ "Aggregation:\n"
+ " count\n"
+ " min\n"
+ " max\n"
+ " avg\n"
+ "\n");
+ exit (status);
+} /* }}} void exit_usage */
+
+int show (lcc_connection_t *c, int argc, char **argv) /* {{{ */
+{
+ lcc_identifier_t tmp;
+ int status;
+ int i;
+ size_t j;
+
+ if (argc < 3)
+ exit_usage (EXIT_FAILURE);
+
+ memset (&tmp, 0, sizeof (tmp));
+ status = lcc_string_to_identifier (c, &tmp, argv[1]);
+ if (status != 0)
+ return (status);
+ selector = &tmp;
+
+ for (i = 2; i < argc; i++)
+ aggregation_type_add (argv[i]);
+
+ status = read_data (c);
+ if (status != 0)
+ return (status);
+
+ status = write_data ();
+ if (status != 0)
+ return (status);
+
+ for (j = 0; j < aggregation_groups_num; j++)
+ free (aggregation_groups[j].name);
+ free (aggregation_groups);
+ free (aggregation_types);
+
+ return (0);
+} /* }}} int show */
+
+/* vim: set sw=2 ts=2 tw=78 expandtab fdm=marker : */
diff --git a/src/collectdctl.c b/src/collectdctl.c
index 3bd8f0410f846ddf21130bf93ac8059555a7c269..f18aa9494a9cead9522c861edd9e597430da18f6 100644 (file)
--- a/src/collectdctl.c
+++ b/src/collectdctl.c
extern char *optarg;
extern int optind;
+/* This function is implemented in collectdctl-show.c, because it requires
+ * some state which is kept in global variables there. */
+int show (lcc_connection_t *c, int argc, char **argv);
+
static void exit_usage (const char *name, int status) {
fprintf ((status == 0) ? stdout : stderr,
"Usage: %s [options] <command> [cmd options]\n\n"
" * flush [timeout=<seconds>] [plugin=<name>] [identifier=<id>]\n"
" * listval\n"
" * putval <identifier> [interval=<seconds>] <value-list(s)>\n"
+ " * show <selector> <aggregation> [<aggregation> ...]\n"
"\nIdentifiers:\n\n"
status = listval (c, argc - optind, argv + optind);
else if (strcasecmp (argv[optind], "putval") == 0)
status = putval (c, argc - optind, argv + optind);
+ else if (strcasecmp (argv[optind], "show") == 0)
+ status = show (c, argc - optind, argv + optind);
else {
fprintf (stderr, "%s: invalid command: %s\n", argv[0], argv[optind]);
return (1);
} /* main */
/* vim: set sw=2 ts=2 tw=78 expandtab : */
-
diff --git a/src/collectdctl.pod b/src/collectdctl.pod
index 21c0b500c37d36c55b7d90a97674206a8065f876..1933c80c90bdfa67c0700553a92c40dabacd7519 100644 (file)
--- a/src/collectdctl.pod
+++ b/src/collectdctl.pod
data-set definition specified by the type as given in the identifier (see
L<types.db(5)> for details).
+=item B<show> I<E<lt>selectorE<gt>> I<E<lt>aggregationE<gt>> [I<E<lt>aggregationE<gt>> ...]
+
+Show values or an aggregation of values. The I<selector> selects which values
+to show. It is basically an I<identifier> with special meaning for C<+> and
+C<*>. If a hostname, plugin, type or one of the two instances is C<+> or C<*>,
+any string will match the selector. The difference between the two is that all
+identifiers with the same substitution for C<+> are grouped and aggregated by
+the specified aggregation function(s). For example, the selector
+
+ +/cpu-*/cpu-+
+
+will return the CPU states of each host, aggregated over all CPUs of each
+system. Please see L</"IDENTIFIERS"> for a description of identifiers.
+
+The I<aggregation> setting defines how multiple values are combined into one
+value. Valid values are:
+
+=over 4
+
+=item B<count>
+
+Number of non-NAN values. This value may be zero if all individual values are
+NAN.
+
+=item B<min>
+
+Minimum value.
+
+=item B<max>
+
+Maximum value.
+
+=item B<avg>
+
+Average of all values.
+
+=item B<sum>
+
+Sum of all values.
+
+=item B<sdev>
+
+Standard deviation of all non-NAN values. The standard deviation is NAN if
+there were no non-NAN values (B<count> reportsE<nbsp>0) and zero if there was
+exactly one non-NAN value.
+
+=back
+
=back
=head1 IDENTIFIERS