report: implement filtering for metrics

This commit is contained in:
Lennart Poettering
2026-02-20 08:25:12 +01:00
parent ec2cd403f4
commit e83cbc9372
2 changed files with 119 additions and 18 deletions
+12 -5
View File
@@ -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
View File
@@ -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 },
{}
};