From 734b821f65c7ba2311ed67983b0ce41fe6dd0720 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Wed, 16 Mar 2011 22:51:35 +0100 Subject: [PATCH] unixsock plugin: Add filtering regexen to the LISTVAL command. --- src/collectd-unixsock.pod | 24 ++++- src/utils_cmd_listval.c | 199 ++++++++++++++++++++++++++++++++++---- src/utils_parse_option.c | 2 +- 3 files changed, 204 insertions(+), 21 deletions(-) diff --git a/src/collectd-unixsock.pod b/src/collectd-unixsock.pod index 83802a14..265c41c7 100644 --- a/src/collectd-unixsock.pod +++ b/src/collectd-unixsock.pod @@ -59,7 +59,7 @@ Example: <- | 1 Value found <- | value=1.260000e+00 -=item B +=item B [I] Returns a list of the values available in the value cache together with the time of the last update, so that querying applications can issue a B @@ -78,6 +78,28 @@ Example: <- | 1182204284 myhost/cpu-0/cpu-user ... +Valid options are B, B, B, B and +B. Each option takes a regular expression as its argument. If at +least one regular expression is supplied, only values where each appropriate +part of the identifier satisfies the given regular expression are returned. + +Example: + -> | LISTVAL plugin_instance="^$" + <- | 5 Matching values + <- | 1300311250.802 myhost/load/load + <- | 1300311250.802 myhost/memory/memory-buffered + <- | 1300311250.802 myhost/memory/memory-cached + <- | 1300311250.804 myhost/memory/memory-free + <- | 1300311250.802 myhost/memory/memory-used + +Regular expressions follow the I syntax (ERE) +described in L and will match case-sensitive. It is recommended to +enclose the regular expression in double quotes. Please note that you will +need to escape the backslash and double quote characters in this case. To +request all values from hosts with the German top level domain ".de", for +example, use: + LISTVAL host="\\.de$" + =item B I [I] I Submits one or more values (identified by I, see below) to the diff --git a/src/utils_cmd_listval.c b/src/utils_cmd_listval.c index ef66af56..078a5275 100644 --- a/src/utils_cmd_listval.c +++ b/src/utils_cmd_listval.c @@ -1,6 +1,6 @@ /** * collectd - src/utils_cms_listval.c - * Copyright (C) 2008 Florian octo Forster + * Copyright (C) 2008-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 @@ -16,18 +16,21 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Author: - * Florian octo Forster + * Florian "octo" Forster **/ #include "collectd.h" #include "common.h" #include "plugin.h" +#include + #include "utils_cmd_listval.h" #include "utils_cache.h" #include "utils_parse_option.h" -#define free_everything_and_return(status) do { \ +/* Not very nice, but oh so handy ... */ +#define FREE_EVERYTHING_AND_RETURN(status) do { \ size_t j; \ for (j = 0; j < number; j++) { \ sfree(names[j]); \ @@ -35,6 +38,11 @@ } \ sfree(names); \ sfree(times); \ + if (have_re_host) { regfree (&re_host); } \ + if (have_re_plugin) { regfree (&re_plugin); } \ + if (have_re_plugin_instance) { regfree (&re_plugin_instance); } \ + if (have_re_type) { regfree (&re_type); } \ + if (have_re_type_instance) { regfree (&re_type_instance); } \ return (status); \ } while (0) @@ -42,8 +50,8 @@ if (fprintf (fh, __VA_ARGS__) < 0) { \ char errbuf[1024]; \ WARNING ("handle_listval: failed to write to socket #%i: %s", \ - fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \ - free_everything_and_return (-1); \ + fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \ + FREE_EVERYTHING_AND_RETURN (-1); \ } int handle_listval (FILE *fh, char *buffer) @@ -55,6 +63,17 @@ int handle_listval (FILE *fh, char *buffer) size_t i; int status; + regex_t re_host; + regex_t re_plugin; + regex_t re_plugin_instance; + regex_t re_type; + regex_t re_type_instance; + _Bool have_re_host = 0; + _Bool have_re_plugin = 0; + _Bool have_re_plugin_instance = 0; + _Bool have_re_type = 0; + _Bool have_re_type_instance = 0; + DEBUG ("utils_cmd_listval: handle_listval (fh = %p, buffer = %s);", (void *) fh, buffer); @@ -63,37 +82,179 @@ int handle_listval (FILE *fh, char *buffer) if (status != 0) { print_to_socket (fh, "-1 Cannot parse command.\n"); - free_everything_and_return (-1); + FREE_EVERYTHING_AND_RETURN (-1); } assert (command != NULL); if (strcasecmp ("LISTVAL", command) != 0) { print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command); - free_everything_and_return (-1); + FREE_EVERYTHING_AND_RETURN (-1); } - if (*buffer != 0) + /* Parse the options which may still be contained in the buffer. Valid + * options are "host", "plugin", "plugin_instance", "type" and + * "type_instance"; each option takes a regular expression as argument which + * is used to filter the returned identifiers. */ + while (*buffer != 0) { - print_to_socket (fh, "-1 Garbage after end of command: %s\n", buffer); - free_everything_and_return (-1); - } + char *opt_key; + char *opt_value; + regex_t *re; + _Bool *have_re; + + opt_key = NULL; + opt_value = NULL; + status = parse_option (&buffer, &opt_key, &opt_value); + if (status != 0) + { + print_to_socket (fh, "-1 Parsing options failed.\n"); + FREE_EVERYTHING_AND_RETURN (-1); + } + if (strcasecmp ("host", opt_key) == 0) + { + re = &re_host; + have_re = &have_re_host; + } + else if (strcasecmp ("plugin", opt_key) == 0) + { + re = &re_plugin; + have_re = &have_re_plugin; + } + else if (strcasecmp ("plugin_instance", opt_key) == 0) + { + re = &re_plugin_instance; + have_re = &have_re_plugin_instance; + } + else if (strcasecmp ("type", opt_key) == 0) + { + re = &re_type; + have_re = &have_re_type; + } + else if (strcasecmp ("type_instance", opt_key) == 0) + { + re = &re_type_instance; + have_re = &have_re_type_instance; + } + else + { + print_to_socket (fh, "-1 Unknown option: %s\n", opt_key); + FREE_EVERYTHING_AND_RETURN (-1); + } + + /* Free a previous regular expression */ + if (*have_re) + { + NOTICE ("listval command: More than one match for part \"%s\". " + "Only the last regular expression will be used to search " + "for matching value lists!", + opt_key); + regfree (re); + *have_re = 0; + } + + /* Compile the regular expression. */ + status = regcomp (re, opt_value, REG_EXTENDED | REG_NOSUB); + if (status != 0) + { + char errbuf[1024]; + regerror (status, re, errbuf, sizeof (errbuf)); + errbuf[sizeof (errbuf) - 1] = 0; + print_to_socket (fh, "-1 Compiling %s regex failed: %s\n", + opt_key, errbuf); + FREE_EVERYTHING_AND_RETURN (-1); + } + *have_re = 1; + } /* while (*buffer != 0) */ + + /* Get a list of values from the cache. */ status = uc_get_names (&names, ×, &number); if (status != 0) { DEBUG ("command listval: uc_get_names failed with status %i", status); print_to_socket (fh, "-1 uc_get_names failed.\n"); - free_everything_and_return (-1); + FREE_EVERYTHING_AND_RETURN (-1); + } + + /* If no regex has been specified, take the easy way out. This will avoid a + * lot of pointless if-blocks. */ + if (!have_re_host + && !have_re_plugin + && !have_re_plugin_instance + && !have_re_type + && !have_re_type_instance) + { + print_to_socket (fh, "%i Value%s found\n", + (int) number, (number == 1) ? "" : "s"); + for (i = 0; i < number; i++) + print_to_socket (fh, "%.3f %s\n", CDTIME_T_TO_DOUBLE (times[i]), + names[i]); } + else /* At least one regular expression is present. */ + { + char *matching_names[number]; + cdtime_t matching_times[number]; + size_t matching_number = 0; + + /* We need to figure out how many values we're going to return for the + * status line first. We save all matched values in the above arrays to + * avoid doing the matching twice. */ + for (i = 0; i < number; i++) + { + value_list_t vl = VALUE_LIST_INIT; - print_to_socket (fh, "%i Value%s found\n", - (int) number, (number == 1) ? "" : "s"); - for (i = 0; i < number; i++) - print_to_socket (fh, "%.3f %s\n", CDTIME_T_TO_DOUBLE (times[i]), - names[i]); + status = parse_identifier_vl (names[i], &vl); + if (status != 0) + continue; + + /* If a regex exists and doesn't match, ignore this value and continue + * with the next one. */ + if (have_re_host && (regexec (&re_host, + /* string = */ vl.host, + /* nmatch = */ 0, + /* pmatch = */ NULL, + /* flags = */ 0) == REG_NOMATCH)) + continue; + if (have_re_plugin && (regexec (&re_plugin, + /* string = */ vl.plugin, + /* nmatch = */ 0, + /* pmatch = */ NULL, + /* flags = */ 0) == REG_NOMATCH)) + continue; + if (have_re_plugin_instance && (regexec (&re_plugin_instance, + /* string = */ vl.plugin_instance, + /* nmatch = */ 0, + /* pmatch = */ NULL, + /* flags = */ 0) == REG_NOMATCH)) + continue; + if (have_re_type && (regexec (&re_type, + /* string = */ vl.type, + /* nmatch = */ 0, + /* pmatch = */ NULL, + /* flags = */ 0) == REG_NOMATCH)) + continue; + if (have_re_type_instance && (regexec (&re_type_instance, + /* string = */ vl.type_instance, + /* nmatch = */ 0, + /* pmatch = */ NULL, + /* flags = */ 0) == REG_NOMATCH)) + continue; + + matching_names[matching_number] = names[i]; + matching_times[matching_number] = times[i]; + matching_number++; + } + + print_to_socket (fh, "%zu Matching value%s\n", + matching_number, (matching_number == 1) ? "" : "s"); + for (i = 0; i < matching_number; i++) + print_to_socket (fh, "%.3f %s\n", + CDTIME_T_TO_DOUBLE (matching_times[i]), + matching_names[i]); + } - free_everything_and_return (0); + FREE_EVERYTHING_AND_RETURN (0); } /* int handle_listval */ -/* vim: set sw=2 sts=2 ts=8 : */ +/* vim: set sw=2 sts=2 ts=8 et : */ diff --git a/src/utils_parse_option.c b/src/utils_parse_option.c index 2168cd1a..6bb9d1f2 100644 --- a/src/utils_parse_option.c +++ b/src/utils_parse_option.c @@ -127,7 +127,7 @@ int parse_option (char **ret_buffer, char **ret_key, char **ret_value) /* Look for the equal sign */ buffer = key; - while (isalnum ((int) *buffer)) + while (isalnum ((int) *buffer) || (*buffer == '_')) buffer++; if ((*buffer != '=') || (buffer == key)) return (1); -- 2.30.2