report: implement filtering for metrics
This commit is contained in:
+12
-5
@@ -44,19 +44,26 @@
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><command>metrics</command></term>
|
||||
<term><command>metrics</command> <arg choice="opt" rep="repeat">MATCH</arg></term>
|
||||
|
||||
<listitem><para>Acquire a list of metrics values from all local services providing them, and write them to standard output.</para>
|
||||
<listitem><para>Acquire a list of metrics values from all local services providing them, and write
|
||||
them to standard output. Optionally takes one or more match expressions for filtering the metrics to
|
||||
show. The expression either may be a literal metric family name to search for, or a prefix of one
|
||||
(which will be matched only at dot boundaries). If multiple matches are specified as multiple
|
||||
paramaters, any metric matching <emphasis>any</emphasis> of the specified matches are shown.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>describe-metrics</command></term>
|
||||
<term><command>describe-metrics</command> <arg choice="opt" rep="repeat">MATCH</arg></term>
|
||||
|
||||
<listitem><para>Acquire a list of metric families from all local services providing them, and write
|
||||
them to standard output. This returns information about metrics, their data types and human readable
|
||||
description without values.</para>
|
||||
them to standard output. This returns primarily static information about metrics, their data types
|
||||
and human readable description, without values.</para>
|
||||
|
||||
<para>Match expressions similar to those supported by <command>metrics</command> are supported for
|
||||
<command>describe-metrics</command>, too.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
+107
-13
@@ -33,6 +33,9 @@
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
|
||||
static char **arg_matches = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep);
|
||||
|
||||
typedef enum Action {
|
||||
ACTION_LIST,
|
||||
@@ -147,7 +150,13 @@ static bool metric_startswith_prefix(const char *metric_name, const char *prefix
|
||||
return !isempty(m) && m[0] == '.';
|
||||
}
|
||||
|
||||
static bool metrics_validate_one(LinkInfo *li, sd_json_variant *metric) {
|
||||
typedef enum {
|
||||
VERDICT_INVALID,
|
||||
VERDICT_MATCH,
|
||||
VERDICT_MISMATCH,
|
||||
} Verdict;
|
||||
|
||||
static Verdict metrics_verdict(LinkInfo *li, sd_json_variant *metric) {
|
||||
int r;
|
||||
|
||||
assert(li);
|
||||
@@ -162,20 +171,48 @@ static bool metrics_validate_one(LinkInfo *li, sd_json_variant *metric) {
|
||||
r = sd_json_dispatch(metric, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &metric_name);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to get metric name, assuming name is not valid: %m");
|
||||
return false;
|
||||
return VERDICT_INVALID;
|
||||
}
|
||||
|
||||
/* Validate metric name is generally valid */
|
||||
r = metrics_name_valid(metric_name);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to determine if '%s' is a valid metric name: %m", metric_name);
|
||||
return false;
|
||||
return VERDICT_INVALID;
|
||||
}
|
||||
if (!r) {
|
||||
log_debug("Metric name '%s' is not valid, skipping.", metric_name);
|
||||
return false;
|
||||
return VERDICT_INVALID;
|
||||
}
|
||||
|
||||
return metric_startswith_prefix(metric_name, li->metric_prefix);
|
||||
/* Validate metric name matches the Varlink service it was found on */
|
||||
if (!metric_startswith_prefix(metric_name, li->metric_prefix)) {
|
||||
log_debug("Metric name '%s' does not match service name '%s', skipping.", metric_name, li->metric_prefix);
|
||||
return VERDICT_INVALID;
|
||||
}
|
||||
|
||||
/* Check it against any specified matches */
|
||||
bool matches;
|
||||
if (strv_isempty(arg_matches))
|
||||
matches = true;
|
||||
else {
|
||||
matches = false;
|
||||
|
||||
/* Allow exact matches or prefix matches */
|
||||
STRV_FOREACH(i, arg_matches)
|
||||
if (streq(metric_name, *i) ||
|
||||
metric_startswith_prefix(metric_name, *i)) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matches) {
|
||||
log_debug("Metric '%s' does not match search, ignoring.", metric_name);
|
||||
return VERDICT_MISMATCH;
|
||||
}
|
||||
|
||||
return VERDICT_MATCH;
|
||||
}
|
||||
|
||||
static int metrics_on_query_reply(
|
||||
@@ -206,10 +243,13 @@ static int metrics_on_query_reply(
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!metrics_validate_one(li, parameters)) {
|
||||
Verdict v = metrics_verdict(li, parameters);
|
||||
if (v == VERDICT_INVALID) {
|
||||
context->n_invalid_metrics++;
|
||||
goto finish;
|
||||
}
|
||||
if (v == VERDICT_MISMATCH)
|
||||
goto finish;
|
||||
|
||||
/* Collect metrics for later sorting */
|
||||
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
|
||||
@@ -442,6 +482,52 @@ static int metrics_output(Context *context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_metrics_matches(char **matches) {
|
||||
int r;
|
||||
|
||||
STRV_FOREACH(i, matches) {
|
||||
r = metrics_name_valid(*i);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if '%s' is a valid metric name: %m", *i);
|
||||
if (!r && !varlink_idl_interface_name_is_valid(*i))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Match is not a valid family name or prefix: %s", *i);
|
||||
|
||||
if (strv_extend(&arg_matches, *i) < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
strv_sort_uniq(arg_matches);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool test_service_matches(const char *service) {
|
||||
assert(service);
|
||||
|
||||
if (strv_isempty(arg_matches))
|
||||
return true;
|
||||
|
||||
/* Only contact services whose name is either a prefix of any of the specified metrics families, or
|
||||
* if the specified metric families are a prefix of the service.
|
||||
*
|
||||
* Example: if user specifies "foo" we want to match sockets "foo" and "foo.bar".
|
||||
* if user specifies "foo.waldo" we want to match sockets "foo" and "foo.waldo" as well as "foo.waldo.quux".
|
||||
*
|
||||
* in other words: it should be fine to specify any prefix of a service name to get all metrics from all matching services.
|
||||
* it should also be fine to specify a full metric name, and then go directly to the relevant services, and ask for matching metrics.
|
||||
*/
|
||||
|
||||
STRV_FOREACH(i, arg_matches) {
|
||||
if (streq(service, *i))
|
||||
return true;
|
||||
|
||||
if (metric_startswith_prefix(*i, service) ||
|
||||
metric_startswith_prefix(service, *i))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int readdir_sources(char **ret_directory, DirectoryEntries **ret) {
|
||||
int r;
|
||||
|
||||
@@ -478,6 +564,9 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) {
|
||||
if (!varlink_idl_interface_name_is_valid(d->d_name))
|
||||
continue;
|
||||
|
||||
if (!test_service_matches(d->d_name))
|
||||
continue;
|
||||
|
||||
de->entries[m++] = *i;
|
||||
}
|
||||
|
||||
@@ -493,7 +582,7 @@ static int verb_metrics(int argc, char *argv[], void *userdata) {
|
||||
Action action;
|
||||
int r;
|
||||
|
||||
assert(argc == 1);
|
||||
assert(argc >= 1);
|
||||
assert(argv);
|
||||
|
||||
if (streq_ptr(argv[0], "metrics"))
|
||||
@@ -503,6 +592,10 @@ static int verb_metrics(int argc, char *argv[], void *userdata) {
|
||||
action = ACTION_DESCRIBE;
|
||||
}
|
||||
|
||||
r = parse_metrics_matches(argv + 1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(context_done) Context context = {
|
||||
.action = action,
|
||||
};
|
||||
@@ -633,8 +726,9 @@ static int verb_help(int argc, char *argv[], void *userdata) {
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sAcquire metrics from local sources.%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" metrics Acquire list of metrics and their values\n"
|
||||
" describe-metrics Describe available metrics\n"
|
||||
" metrics [MATCH...] Acquire list of metrics and their values\n"
|
||||
" describe-metrics [MATCH...]\n"
|
||||
" Describe available metrics\n"
|
||||
" list-sources Show list of known metrics sources\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
@@ -725,10 +819,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
static int report_main(int argc, char *argv[]) {
|
||||
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, 1, 0, verb_help },
|
||||
{ "metrics", VERB_ANY, 1, 0, verb_metrics },
|
||||
{ "describe-metrics", VERB_ANY, 1, 0, verb_metrics },
|
||||
{ "list-sources", VERB_ANY, 1, 0, verb_list_sources },
|
||||
{ "help", VERB_ANY, 1, 0, verb_help },
|
||||
{ "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics },
|
||||
{ "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics },
|
||||
{ "list-sources", VERB_ANY, 1, 0, verb_list_sources },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user