]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
report: implement filtering for metrics
authorLennart Poettering <lennart@amutable.com>
Wed, 18 Feb 2026 13:53:08 +0000 (14:53 +0100)
committerLennart Poettering <lennart@amutable.com>
Fri, 20 Feb 2026 07:25:12 +0000 (08:25 +0100)
man/systemd-report.xml
src/report/report.c

index 010cf2e2f16c67127f0b3938c33d4c2c439f3a0d..2b4dfeb51c7e66a4740d2ec500bfda29bd998623 100644 (file)
 
     <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>
index 562fe6653da8fec2bdb6b0081d9067921a9c3432..d59c3b92143926f89beb72edb2e689698fdb1836 100644 (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 },
                 {}
         };