From e5687f689f20b051420fe0154ea4391af697ed7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 23:36:15 +0200 Subject: [PATCH 1/6] report: absorb "facts" into "metrics" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This gets rid of the duality in the cmdline interface and various APIs. The general plan is collect both "facts" and "metrics" in a single list. We have various producers which respond on the io.systemd.Facts endpoint. Those will need to be adjusted to respond to io.systemd.Metrics. Cmdline interface: 'metrics' (unchanged) 'describe-metrics' → 'describe' 'facts' → merged into 'metrics' 'describe-facts' → merged into 'describe' --- man/systemd-report.xml | 10 +- src/report/report-upload.c | 10 +- src/report/report.c | 225 +----------------- src/report/report.h | 7 +- .../fake-report-server.py | 4 +- test/units/TEST-74-AUX-UTILS.report.sh | 36 +-- 6 files changed, 27 insertions(+), 265 deletions(-) diff --git a/man/systemd-report.xml b/man/systemd-report.xml index b53a50c2f8..f14600dfe5 100644 --- a/man/systemd-report.xml +++ b/man/systemd-report.xml @@ -18,7 +18,7 @@ systemd-report - Generate report of system facts and metrics + Generate report of system metrics @@ -33,7 +33,7 @@ Note: this command is experimental for now. While it is likely to become a regular component of systemd, it might still change in behaviour and interface. - systemd-report requests facts and metrics from the system and writes them to + systemd-report requests metrics from the system and writes them to standard output. @@ -56,14 +56,14 @@ - describe-metrics MATCH + describe MATCH Acquire a list of metric families from all local services providing them, and write them to standard output. This returns primarily static information about metrics, their data types and human readable description, without values. - Match expressions similar to those supported by metrics are supported for - describe-metrics, too. + Match expressions supported by metrics are supported by + describe too. diff --git a/src/report/report-upload.c b/src/report/report-upload.c index c64bf86e13..486e815e8d 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -59,18 +59,10 @@ static int build_json_report(Context *context, sd_json_variant **ret) { usec_t ts = now(CLOCK_REALTIME); int r; - const char *ident; - if (IN_SET(context->action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)) - ident = "metrics"; - else if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)) - ident = "facts"; - else - assert_not_reached(); - r = sd_json_buildo(ret, SD_JSON_BUILD_PAIR("timestamp", SD_JSON_BUILD_STRING(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_UTC))), - SD_JSON_BUILD_PAIR(ident, + SD_JSON_BUILD_PAIR("metrics", SD_JSON_BUILD_VARIANT_ARRAY(context->metrics, context->n_metrics))); if (r < 0) return log_error_errno(r, "Failed to build JSON data: %m"); diff --git a/src/report/report.c b/src/report/report.c index fef01c094e..feedcaa43a 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -28,8 +28,8 @@ #include "verbs.h" #include "web-util.h" -#define METRICS_OR_FACTS_MAX 4096U -#define METRICS_OR_FACTS_LINKS_MAX 128U +#define METRICS_MAX 4096U +#define METRICS_LINKS_MAX 128U #define TIMEOUT_USEC (30 * USEC_PER_SEC) /* 30 seconds */ static PagerFlags arg_pager_flags = 0; @@ -87,8 +87,6 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( static const char* const action_method_table[] = { [ACTION_LIST_METRICS] = "io.systemd.Metrics.List", [ACTION_DESCRIBE_METRICS] = "io.systemd.Metrics.Describe", - [ACTION_LIST_FACTS] = "io.systemd.Facts.List", - [ACTION_DESCRIBE_FACTS] = "io.systemd.Facts.Describe", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(action_method, Action); @@ -249,7 +247,7 @@ static int on_query_reply( goto finish; } - if (context->n_metrics >= METRICS_OR_FACTS_MAX) { + if (context->n_metrics >= METRICS_MAX) { context->n_skipped_metrics++; goto finish; } @@ -436,107 +434,6 @@ static int output_collected_describe(Context *context, Table **ret) { return 0; } -static int facts_output_list(Context *context, Table **ret) { - int r; - - assert(context); - assert(ret); - - _cleanup_(table_unrefp) Table *table = table_new("family", "object", "value"); - if (!table) - return log_oom(); - - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - table_set_sort(table, (size_t) 0, (size_t) 1, (size_t) 2); - - FOREACH_ARRAY(m, context->metrics, context->n_metrics) { - struct { - const char *name; - const char *object; - sd_json_variant *value; - } d = {}; - - static const sd_json_dispatch_field dispatch_table[] = { - { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, name), SD_JSON_MANDATORY }, - { "object", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, object), 0 }, - { "value", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant_noref, voffsetof(d, value), SD_JSON_MANDATORY }, - {} - }; - - r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d); - if (r < 0) { - _cleanup_free_ char *t = NULL; - int k = sd_json_variant_format(*m, /* flags= */ 0, &t); - if (k < 0) - return log_error_errno(k, "Failed to format JSON: %m"); - - log_warning_errno(r, "Cannot parse fact, skipping: %s", t); - continue; - } - - r = table_add_many( - table, - TABLE_STRING, d.name, - TABLE_STRING, d.object, - TABLE_JSON, d.value, - TABLE_SET_WEIGHT, 50U); - if (r < 0) - return table_log_add_error(r); - } - - *ret = TAKE_PTR(table); - return 0; -} - -static int facts_output_describe(Context *context, Table **ret) { - int r; - - assert(context); - assert(ret); - - _cleanup_(table_unrefp) Table *table = table_new("family", "description"); - if (!table) - return log_oom(); - - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - table_set_sort(table, (size_t) 0, (size_t) 1); - - FOREACH_ARRAY(m, context->metrics, context->n_metrics) { - struct { - const char *name; - const char *description; - } d = {}; - - static const sd_json_dispatch_field dispatch_table[] = { - { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, name), SD_JSON_MANDATORY }, - { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, description), 0 }, - {} - }; - - r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d); - if (r < 0) { - _cleanup_free_ char *t = NULL; - int k = sd_json_variant_format(*m, /* flags= */ 0, &t); - if (k < 0) - return log_error_errno(k, "Failed to format JSON: %m"); - - log_warning_errno(r, "Cannot parse fact description, skipping: %s", t); - continue; - } - - r = table_add_many( - table, - TABLE_STRING, d.name, - TABLE_STRING, d.description, - TABLE_SET_WEIGHT, 50U); - if (r < 0) - return table_log_add_error(r); - } - - *ret = TAKE_PTR(table); - return 0; -} - static int output_collected(Context *context) { int r; @@ -555,13 +452,8 @@ static int output_collected(Context *context) { return log_error_errno(r, "Failed to write JSON: %m"); } - if (context->n_metrics == 0 && arg_legend) { - if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)) - log_info("No facts collected."); - else - log_info("No metrics collected."); - } - + if (context->n_metrics == 0 && arg_legend) + log_info("No metrics collected."); return 0; } @@ -577,14 +469,6 @@ static int output_collected(Context *context) { r = output_collected_describe(context, &table); break; - case ACTION_LIST_FACTS: - r = facts_output_list(context, &table); - break; - - case ACTION_DESCRIBE_FACTS: - r = facts_output_describe(context, &table); - break; - default: assert_not_reached(); } @@ -598,12 +482,10 @@ static int output_collected(Context *context) { } if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { - const char *type = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS) ? "facts" : "metrics"; - if (table_isempty(table)) - printf("No %s available.\n", type); + printf("No metrics available.\n"); else - printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type); + printf("\n%zu metrics listed.\n", table_get_rows(table) - 1); } return 0; @@ -707,7 +589,7 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { VERB_FULL(verb_metrics, "metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_METRICS, "Acquire list of metrics and their values"); -VERB_FULL(verb_metrics, "describe-metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS, +VERB_FULL(verb_metrics, "describe", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS, "Describe available metrics"); static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) { Action action = data; @@ -746,7 +628,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) FOREACH_ARRAY(i, de->entries, de->n_entries) { struct dirent *d = *i; - if (set_size(context.link_infos) >= METRICS_OR_FACTS_LINKS_MAX) { + if (set_size(context.link_infos) >= METRICS_LINKS_MAX) { n_skipped_sources++; break; } @@ -792,93 +674,6 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) return 0; } -VERB_FULL(verb_facts, "facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_FACTS, - "Acquire list of facts and their values"); -VERB_FULL(verb_facts, "describe-facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_FACTS, - "Describe available facts"); -static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) { - Action action = data; - int r; - - assert(argc >= 1); - assert(argv); - assert(IN_SET(action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)); - - /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ - arg_json_format_flags |= SD_JSON_FORMAT_SEQ; - - r = parse_metrics_matches(argv + 1); - if (r < 0) - return r; - - _cleanup_(context_done) Context context = { - .action = action, - }; - size_t n_skipped_sources = 0; - - _cleanup_free_ DirectoryEntries *de = NULL; - _cleanup_free_ char *sources_path = NULL; - r = readdir_sources(&sources_path, &de); - if (r < 0) - return r; - if (r > 0) { - r = sd_event_default(&context.event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_event_set_signal_exit(context.event, true); - if (r < 0) - return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); - - FOREACH_ARRAY(i, de->entries, de->n_entries) { - struct dirent *d = *i; - - if (set_size(context.link_infos) >= METRICS_OR_FACTS_LINKS_MAX) { - n_skipped_sources++; - break; - } - - _cleanup_free_ char *p = path_join(sources_path, d->d_name); - if (!p) - return log_oom(); - - (void) call_collect(&context, d->d_name, p); - } - } - - if (set_isempty(context.link_infos)) { - if (arg_legend) - log_info("No facts sources found."); - } else { - assert(context.event); - - r = sd_event_loop(context.event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - if (arg_url) - r = upload_collected(&context); - else - r = output_collected(&context); - if (r < 0) - return r; - } - - if (n_skipped_sources > 0) - return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), - "Too many facts sources, only %u sources contacted, %zu sources skipped.", - set_size(context.link_infos), n_skipped_sources); - if (context.n_invalid_metrics > 0) - return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), - "%zu facts are not valid.", - context.n_invalid_metrics); - if (context.n_skipped_metrics > 0) - return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), - "Too many facts, only %zu facts collected, %zu facts skipped.", - context.n_metrics, context.n_skipped_metrics); - return 0; -} - VERB_NOARG(verb_list_sources, "list-sources", "Show list of known metrics sources"); static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -955,7 +750,7 @@ static int help(void) { (void) table_sync_column_widths(0, options, verbs); printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sAcquire metrics and facts from local sources.%s\n" + "\n%sAcquire metrics from local sources.%s\n" "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), diff --git a/src/report/report.h b/src/report/report.h index 4d7b5bdd3f..4adb203495 100644 --- a/src/report/report.h +++ b/src/report/report.h @@ -16,19 +16,16 @@ extern usec_t arg_network_timeout_usec; typedef enum Action { ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS, - ACTION_LIST_FACTS, - ACTION_DESCRIBE_FACTS, _ACTION_MAX, _ACTION_INVALID = -EINVAL, } Action; -/* The structure for collected "metrics" or "facts". The fields - * are prefixed with just "metrics" for brevity. */ +/* The structure for collected "metrics". */ typedef struct Context { Action action; sd_event *event; Set *link_infos; - sd_json_variant **metrics; /* Collected metrics or facts for sorting */ + sd_json_variant **metrics; /* Collected metrics for sorting */ size_t n_metrics, n_skipped_metrics, n_invalid_metrics; struct iovec_wrapper upload_answer; } Context; diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py index 4875a00bad..6beb6383c2 100755 --- a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py @@ -35,8 +35,8 @@ class Handler(BaseHTTPRequestHandler): print(f"JSON: {s if len(s := str(data)) < 80 else s[:40] + '…' + s[-40:]}") - if "metrics" not in data and "facts" not in data: - self.send_error(400, "Missing 'metrics' or 'facts' field") + if "metrics" not in data: + self.send_error(400, "Missing 'metrics' field") return response = json.dumps({"status": "ok"}).encode() diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 53b83c4dd9..73678fcabf 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -16,9 +16,9 @@ REPORT=/usr/lib/systemd/systemd-report "$REPORT" metrics "$REPORT" metrics -j "$REPORT" metrics --no-legend -"$REPORT" describe-metrics -"$REPORT" describe-metrics -j -"$REPORT" describe-metrics --no-legend +"$REPORT" describe +"$REPORT" describe -j +"$REPORT" describe --no-legend "$REPORT" list-sources "$REPORT" list-sources -j "$REPORT" list-sources --no-legend @@ -26,9 +26,9 @@ REPORT=/usr/lib/systemd/systemd-report "$REPORT" metrics io "$REPORT" metrics io.systemd piff "$REPORT" metrics piff -"$REPORT" describe-metrics io -"$REPORT" describe-metrics io.systemd piff -"$REPORT" describe-metrics piff +"$REPORT" describe io +"$REPORT" describe io.systemd piff +"$REPORT" describe piff # test io.systemd.CGroup Metrics systemctl start systemd-report-cgroup.socket @@ -46,26 +46,6 @@ varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics # Make sure the service for "system facts" is enabled systemctl start systemd-report-basic.socket -# Test facts verbs -"$REPORT" facts -"$REPORT" facts -j -"$REPORT" facts --no-legend -"$REPORT" describe-facts -"$REPORT" describe-facts -j -"$REPORT" describe-facts --no-legend - -# Test facts with match filters -"$REPORT" facts io -"$REPORT" facts io.systemd piff -"$REPORT" facts piff -"$REPORT" describe-facts io -"$REPORT" describe-facts io.systemd piff -"$REPORT" describe-facts piff - -# Test facts via direct Varlink call on existing socket -varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.List {} -varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.Describe {} - # Test HTTP upload (plain http) FAKE_SERVER=/usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py CERTDIR=$(mktemp -d) @@ -81,7 +61,6 @@ systemd-run -p Type=notify --unit=fake-report-server "$FAKE_SERVER" systemctl status fake-report-server "$REPORT" metrics --url=http://localhost:8089/ -"$REPORT" facts --url=http://localhost:8089/ # Test HTTPS upload with generated TLS certificates openssl req -x509 -newkey rsa:2048 -keyout "$CERTDIR/server.key" -out "$CERTDIR/server.crt" \ @@ -91,6 +70,5 @@ systemd-run -p Type=notify --unit=fake-report-server-tls \ "$FAKE_SERVER" --cert="$CERTDIR/server.crt" --key="$CERTDIR/server.key" --port=8090 systemctl status fake-report-server-tls -"$REPORT" metrics --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" -"$REPORT" facts --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ +"$REPORT" metrics --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ --extra-header='Authorization: Bearer magic string' From d508bfb51dd0cc827138d74bb25281f69c458d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 00:53:20 +0200 Subject: [PATCH 2/6] man/systemd-report: document --url= and other options 5bbbe210a4e3856385d95e16074d8aa98cff909b added the options but not the documentation. --- man/systemd-report.xml | 75 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/man/systemd-report.xml b/man/systemd-report.xml index f14600dfe5..0974244a8f 100644 --- a/man/systemd-report.xml +++ b/man/systemd-report.xml @@ -1,6 +1,9 @@ - + + "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ + +%entities; +]> + + + + + Upload the collected report to the specified address instead of writing it to + standard output. URL must point to a server accepting POST requests with + a JSON-formatted report body. + + Note: both http:// and https:// URLs are supported, but + connections over plain HTTP are made without encryption. Thus, this mode should + only be used in specific situations where integrity and confidentiality of the report is not required + or is ensured through some other means. Using https:// is recommended. + + + + + + + + Takes a path to a SSL key file in PEM format, used for client certificate + authentication when uploading. Can also be set to -, to disable client certificate + authentication. Defaults to + &CERTIFICATE_ROOT;/private/systemd-report.pem. + + + + + + + + Takes a path to a SSL certificate file in PEM format, used for client certificate + authentication when uploading. Defaults to + &CERTIFICATE_ROOT;/certs/systemd-report.pem. + + + + + + + + Takes a path to a SSL CA certificate file in PEM format used to verify the server + certificate, or the literal string all to disable certificate checking + entirely. Defaults to &CERTIFICATE_ROOT;/ca/trusted.pem. + + + + + + + + Timeout for the network upload operation. Takes a value in seconds (or in other + time units if suffixed with ms, min, h, + etc.); see + systemd.time7 + for details. Defaults to 30 seconds. + + + + + + + + Inject an additional HTTP header into the upload request. May be specified multiple + times to add several headers. Passing an empty string clears any headers added by previous + uses. + + + From a2186070b79b2dcce332465696ad31241f27cf11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 23:55:48 +0200 Subject: [PATCH 3/6] report: upload reports using a "varlink socket directory" Two new verbs are added: "generate" and "upload". The first one just creates a "report", i.e. puts the metrics into a structured JSON object that in the future is intended to carry additional data like a signature: $ build/systemd-report generate io.systemd.Manager.UnitsTotal { "mediaType" : "application/vnd.io.systemd.report", "timestamp" : "Tue 2026-04-28 22:30:09 UTC", "metrics" : [ { "name" : "io.systemd.Manager.UnitsTotal", "value" : 520 } ] } The second verb can be used to upload or otherwise process the report. It builds on the code added in 0a8560eed873a5f89487630a19db550fdbee3c15. In /run/systemd/metrics-upload/ we expect a set of sockets. We'll call out to each one of them. This allows the data to be processed in custom ways, incl. writing to storage or sending over the network. Each socket must provide a single interface: io.systemd.Metrics.Upload {"report":$data} --- man/systemd-report.xml | 25 ++++++ src/report/report-upload.c | 105 +++++++++++++++++++++---- src/report/report.c | 30 +++---- src/report/report.h | 9 ++- test/units/TEST-74-AUX-UTILS.report.sh | 8 +- 5 files changed, 145 insertions(+), 32 deletions(-) diff --git a/man/systemd-report.xml b/man/systemd-report.xml index 0974244a8f..560d940624 100644 --- a/man/systemd-report.xml +++ b/man/systemd-report.xml @@ -71,6 +71,31 @@ + + generate MATCH + + Acquire a list of metrics and build a JSON report. + + Match expressions supported by metrics are supported here too. + + + + + + upload MATCH + + This command can be used to send the report built by generate + to an external server. Two upload mechanisms are supported. If an http:// or + https:// URL is specified with , an HTTP upload will be + performed to the specified location. Otherwise, any sockets under + /run/systemd/metrics-upload/ will be used to call + io.systemd.Report.Upload(). + + Match expressions supported by metrics are supported here too. + + + + list-sources diff --git a/src/report/report-upload.c b/src/report/report-upload.c index 486e815e8d..fce0c0e551 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -2,12 +2,15 @@ #include "sd-json.h" +#include "alloc-util.h" +#include "errno-util.h" #include "log.h" #include "report.h" #include "string-util.h" #include "strv.h" #include "time-util.h" #include "utf8.h" +#include "varlink-util.h" #include "version.h" #if HAVE_LIBCURL @@ -49,6 +52,7 @@ static size_t output_callback(char *buf, return nmemb; } +#endif static int build_json_report(Context *context, sd_json_variant **ret) { /* Convert the variant array to a JSON report. */ @@ -60,6 +64,7 @@ static int build_json_report(Context *context, sd_json_variant **ret) { int r; r = sd_json_buildo(ret, + SD_JSON_BUILD_PAIR_STRING("mediaType", "application/vnd.io.systemd.report"), SD_JSON_BUILD_PAIR("timestamp", SD_JSON_BUILD_STRING(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_UTC))), SD_JSON_BUILD_PAIR("metrics", @@ -68,9 +73,8 @@ static int build_json_report(Context *context, sd_json_variant **ret) { return log_error_errno(r, "Failed to build JSON data: %m"); return 0; } -#endif -int upload_collected(Context *context) { +static int http_upload_collected(Context *context, sd_json_variant *report) { #if HAVE_LIBCURL _cleanup_(curl_slist_free_allp) struct curl_slist *header = NULL; char error[CURL_ERROR_SIZE] = {}; @@ -81,19 +85,11 @@ int upload_collected(Context *context) { if (r < 0) return r; - { - /* Convert our variant array to a JSON report. - * We won't need the JSON structure again, so free it quickly. */ + /* Upload a JSON report in text form as a single JSON object, instead of a JSON-SEQ list. */ - _cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL; - r = build_json_report(context, &vl); - if (r < 0) - return r; - - r = sd_json_variant_format(vl, /* flags= */ 0, &json); - if (r < 0) - return log_error_errno(r, "Failed to format JSON data: %m"); - } + r = sd_json_variant_format(report, /* flags= */ 0, &json); + if (r < 0) + return log_error_errno(r, "Failed to format JSON data: %m"); r = curl_append_to_header(&header, STRV_MAKE("Content-Type: application/json", @@ -206,3 +202,84 @@ int upload_collected(Context *context) { "Compiled without libcurl."); #endif } + +static int execute_dir_reply( + sd_varlink *link, + sd_json_variant *reply, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + assert(link); + + Context *context = ASSERT_PTR(userdata); + int r; + + if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); + RET_GATHER(context->upload_result, r); + return log_error_errno(r, "Upload via Varlink failed: %s", error_id); + } + + printf("Upload via Varlink was successful; reply: "); + // TODO: once we know what we want to put in the reply, replace the JSON dump by + // some formatted output. + r = sd_json_variant_dump(reply, arg_json_format_flags, stderr, /* prefix= */ ">>> "); + if (r < 0) + return log_error_errno(r, "Failed to dump json object: %m"); + + return 0; +} + +static int upload_collected(Context *context, sd_json_variant *report) { + int r; + + if (arg_url) + return http_upload_collected(context, report); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + r = sd_json_buildo(¶ms, + SD_JSON_BUILD_PAIR_VARIANT("report", report)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + + ssize_t jobs = varlink_execute_directory( + REPORT_UPLOAD_DIR, + "io.systemd.Report.Upload", + params, + /* more= */ false, + arg_network_timeout_usec, + execute_dir_reply, + /* userdata= */ context); + if (jobs < 0) + return log_error_errno(jobs, "Failed to execute upload via %s: %m", REPORT_UPLOAD_DIR); + if (jobs == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "No upload mechanism found via %s.", REPORT_UPLOAD_DIR); + if (context->upload_result < 0) + /* The details were printed at error level by execute_dir_reply above. */ + return log_debug_errno(context->upload_result, "Upload via %s failed: %m", REPORT_UPLOAD_DIR); + + log_debug("Upload via %s finished successfully.", REPORT_UPLOAD_DIR); + return 0; +} + +/* Make a structured report and either print it or upload it. */ +int report_collected(Context *context) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *report = NULL; + int r; + + r = build_json_report(context, &report); + if (r < 0) + return r; + + if (context->action == ACTION_UPLOAD) + return upload_collected(context, report); + + /* Just print the report for now. */ + assert(context->action == ACTION_GENERATE); + r = sd_json_variant_dump(report, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump json object: %m"); + return 0; +} diff --git a/src/report/report.c b/src/report/report.c index feedcaa43a..c23417afb5 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -20,7 +20,6 @@ #include "runtime-scope.h" #include "set.h" #include "sort-util.h" -#include "string-table.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -35,8 +34,8 @@ static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; 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; +sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; char *arg_url = NULL; char *arg_key = NULL; char *arg_cert = NULL; @@ -84,13 +83,6 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( void, trivial_hash_func, trivial_compare_func, LinkInfo, link_info_free); -static const char* const action_method_table[] = { - [ACTION_LIST_METRICS] = "io.systemd.Metrics.List", - [ACTION_DESCRIBE_METRICS] = "io.systemd.Metrics.Describe", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(action_method, Action); - static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b) { const char *name_a, *name_b, *object_a, *object_b; sd_json_variant *fields_a, *fields_b; @@ -300,7 +292,9 @@ static int call_collect(Context *context, const char *name, const char *path) { if (r < 0) return log_error_errno(r, "Failed to bind reply callback: %m"); - const char *method = ASSERT_PTR(action_method_to_string(context->action)); + const char *method = context->action == ACTION_DESCRIBE_METRICS ? + "io.systemd.Metrics.Describe" : + "io.systemd.Metrics.List"; /* This is the method for all other actions. */ r = sd_varlink_observe(vl, method, /* parameters= */ NULL); if (r < 0) @@ -591,16 +585,22 @@ VERB_FULL(verb_metrics, "metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_L "Acquire list of metrics and their values"); VERB_FULL(verb_metrics, "describe", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS, "Describe available metrics"); +VERB_FULL(verb_metrics, "generate", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_GENERATE, + "Build a report with metrics"); +VERB_FULL(verb_metrics, "upload", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_UPLOAD, + "Upload a report with metrics"); static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) { Action action = data; int r; assert(argc >= 1); assert(argv); - assert(IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)); + assert(IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS, ACTION_GENERATE, ACTION_UPLOAD)); - /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ - arg_json_format_flags |= SD_JSON_FORMAT_SEQ; + if (IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)) + /* Enable JSON-SEQ mode for the first two verbs, since we'll dump a large series of JSON + * objects. In the report format, we return a single JSON object, so don't do this. */ + arg_json_format_flags |= SD_JSON_FORMAT_SEQ; r = parse_metrics_matches(argv + 1); if (r < 0) @@ -651,8 +651,8 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); - if (arg_url) - r = upload_collected(&context); + if (IN_SET(action, ACTION_GENERATE, ACTION_UPLOAD)) + r = report_collected(&context); else r = output_collected(&context); if (r < 0) diff --git a/src/report/report.h b/src/report/report.h index 4adb203495..196a3daf57 100644 --- a/src/report/report.h +++ b/src/report/report.h @@ -9,6 +9,9 @@ #define REPORT_CERT_FILE CERTIFICATE_ROOT "/certs/systemd-report.pem" #define REPORT_TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" +#define REPORT_UPLOAD_DIR "/run/systemd/metrics-upload" + +extern sd_json_format_flags_t arg_json_format_flags; extern char *arg_url, *arg_key, *arg_cert, *arg_trust; extern char **arg_extra_headers; extern usec_t arg_network_timeout_usec; @@ -16,6 +19,8 @@ extern usec_t arg_network_timeout_usec; typedef enum Action { ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS, + ACTION_GENERATE, + ACTION_UPLOAD, _ACTION_MAX, _ACTION_INVALID = -EINVAL, } Action; @@ -27,7 +32,9 @@ typedef struct Context { Set *link_infos; sd_json_variant **metrics; /* Collected metrics for sorting */ size_t n_metrics, n_skipped_metrics, n_invalid_metrics; + + int upload_result; struct iovec_wrapper upload_answer; } Context; -int upload_collected(Context *context); +int report_collected(Context *context); diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 73678fcabf..e9332806b3 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -60,7 +60,11 @@ trap at_exit EXIT systemd-run -p Type=notify --unit=fake-report-server "$FAKE_SERVER" systemctl status fake-report-server -"$REPORT" metrics --url=http://localhost:8089/ +"$REPORT" generate io.systemd.Manager.UnitsTotal + +"$REPORT" generate io.systemd.Manager.UnitsTotal | jq . + +"$REPORT" upload --url=http://localhost:8089/ # Test HTTPS upload with generated TLS certificates openssl req -x509 -newkey rsa:2048 -keyout "$CERTDIR/server.key" -out "$CERTDIR/server.crt" \ @@ -70,5 +74,5 @@ systemd-run -p Type=notify --unit=fake-report-server-tls \ "$FAKE_SERVER" --cert="$CERTDIR/server.crt" --key="$CERTDIR/server.key" --port=8090 systemctl status fake-report-server-tls -"$REPORT" metrics --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ +"$REPORT" upload --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ --extra-header='Authorization: Bearer magic string' From 9c336ef6eb9357de71df3163c7a511c9fb470474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 11:42:00 +0200 Subject: [PATCH 4/6] report: set description on varlink sockets We make multiple connections and without this it's hard to know socket we're talking to. --- src/report/report.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/report/report.c b/src/report/report.c index c23417afb5..c0cd920426 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -280,6 +280,10 @@ static int call_collect(Context *context, const char *name, const char *path) { if (r < 0) return log_error_errno(r, "Unable to connect to %s: %m", path); + r = sd_varlink_set_description(vl, name); + if (r < 0) + return log_error_errno(r, "Failed to set varlink description: %m"); + r = sd_varlink_set_relative_timeout(vl, TIMEOUT_USEC); if (r < 0) return log_error_errno(r, "Failed to set varlink timeout: %m"); From ffd4e7ab0148f67c685f18c585ac220fa562079b Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 29 Apr 2026 17:52:50 +0200 Subject: [PATCH 5/6] report: when a report fails, print the json error details When a report upload fails the backend often provides useful details via the varlink error. Show them as part of the upload error message. For now we just dump the json because we have no structure that the backends should follow. We may want to consider adding one (like check for an "error_message" key in the json). But for now this is a nice step forward. --- src/report/report-upload.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/report/report-upload.c b/src/report/report-upload.c index fce0c0e551..471bcb7606 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -218,7 +218,11 @@ static int execute_dir_reply( if (error_id) { r = sd_varlink_error_to_errno(error_id, reply); RET_GATHER(context->upload_result, r); - return log_error_errno(r, "Upload via Varlink failed: %s", error_id); + log_error_errno(r, "Upload via Varlink failed: %s", error_id); + if (reply) + (void) sd_json_variant_dump(reply, arg_json_format_flags, + /* f= */ NULL, /* prefix= */ NULL); + return r; } printf("Upload via Varlink was successful; reply: "); From 628ab0040e163e650a5094e6678a56a2a6b234b8 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 5 May 2026 14:55:18 +0200 Subject: [PATCH 6/6] report: fold io.systemd.Basic facts into metrics We removed the concept of facts, so we need to update the existing io.systemd.Basic facts provider to metrics. This commit does just that. Its mostly mechanical. This also means that facts.{c,h} and varlink-io.systemd.Facts.{c,h} are gone now. --- src/report/report-basic-server.c | 13 ++- src/report/report-basic.c | 74 +++++++----- src/report/report-basic.h | 6 +- src/shared/facts.c | 151 ------------------------- src/shared/facts.h | 31 ----- src/shared/meson.build | 2 - src/shared/varlink-io.systemd.Facts.c | 37 ------ src/shared/varlink-io.systemd.Facts.h | 6 - test/units/TEST-74-AUX-UTILS.report.sh | 6 +- units/systemd-report-basic.socket | 2 +- units/systemd-report-basic@.service.in | 2 +- 11 files changed, 63 insertions(+), 267 deletions(-) delete mode 100644 src/shared/facts.c delete mode 100644 src/shared/facts.h delete mode 100644 src/shared/varlink-io.systemd.Facts.c delete mode 100644 src/shared/varlink-io.systemd.Facts.h diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c index 1e2eca31ea..51de33efe1 100644 --- a/src/report/report-basic-server.c +++ b/src/report/report-basic-server.c @@ -4,12 +4,12 @@ #include "ansi-color.h" #include "build.h" -#include "facts.h" #include "format-table.h" #include "log.h" #include "main-func.h" #include "options.h" #include "report-basic.h" +#include "varlink-io.systemd.Metrics.h" #include "varlink-util.h" static int vl_server(void) { @@ -20,9 +20,16 @@ static int vl_server(void) { if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); - r = facts_add_to_varlink_server(vs, vl_method_list_facts, vl_method_describe_facts); + r = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Metrics); if (r < 0) - return log_error_errno(r, "Failed to register Facts varlink interface: %m"); + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + vs, + "io.systemd.Metrics.List", vl_method_list_metrics, + "io.systemd.Metrics.Describe", vl_method_describe_metrics); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); r = sd_varlink_server_loop_auto(vs); if (r < 0) diff --git a/src/report/report-basic.c b/src/report/report-basic.c index 381262dfd4..50a4dfaaf1 100644 --- a/src/report/report-basic.c +++ b/src/report/report-basic.c @@ -8,21 +8,22 @@ #include "alloc-util.h" #include "architecture.h" -#include "facts.h" #include "hostname-setup.h" +#include "metrics.h" #include "report-basic.h" #include "virt.h" -static int architecture_generate(FactFamilyContext *context, void *userdata) { +static int architecture_generate(MetricFamilyContext *context, void *userdata) { assert(context); - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - architecture_to_string(uname_architecture())); + architecture_to_string(uname_architecture()), + /* fields= */ NULL); } -static int boot_id_generate(FactFamilyContext *context, void *userdata) { +static int boot_id_generate(MetricFamilyContext *context, void *userdata) { sd_id128_t id; int r; @@ -32,13 +33,14 @@ static int boot_id_generate(FactFamilyContext *context, void *userdata) { if (r < 0) return r; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - SD_ID128_TO_STRING(id)); + SD_ID128_TO_STRING(id), + /* fields= */ NULL); } -static int hostname_generate(FactFamilyContext *context, void *userdata) { +static int hostname_generate(MetricFamilyContext *context, void *userdata) { _cleanup_free_ char *hostname = NULL; int r; @@ -48,26 +50,28 @@ static int hostname_generate(FactFamilyContext *context, void *userdata) { if (r < 0) return r; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - hostname); + hostname, + /* fields= */ NULL); } -static int kernel_version_generate(FactFamilyContext *context, void *userdata) { +static int kernel_version_generate(MetricFamilyContext *context, void *userdata) { struct utsname u; assert(context); assert_se(uname(&u) >= 0); - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - u.release); + u.release, + /* fields= */ NULL); } -static int machine_id_generate(FactFamilyContext *context, void *userdata) { +static int machine_id_generate(MetricFamilyContext *context, void *userdata) { sd_id128_t id; int r; @@ -77,13 +81,14 @@ static int machine_id_generate(FactFamilyContext *context, void *userdata) { if (r < 0) return r; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - SD_ID128_TO_STRING(id)); + SD_ID128_TO_STRING(id), + /* fields= */ NULL); } -static int virtualization_generate(FactFamilyContext *context, void *userdata) { +static int virtualization_generate(MetricFamilyContext *context, void *userdata) { Virtualization v; assert(context); @@ -92,51 +97,58 @@ static int virtualization_generate(FactFamilyContext *context, void *userdata) { if (v < 0) return v; - return fact_build_send_string( + return metric_build_send_string( context, /* object= */ NULL, - virtualization_to_string(v)); + virtualization_to_string(v), + /* fields= */ NULL); } -static const FactFamily fact_family_table[] = { - /* Keep facts ordered alphabetically */ +static const MetricFamily metric_family_table[] = { + /* Keep entries ordered alphabetically */ { - .name = FACT_IO_SYSTEMD_BASIC "Architecture", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Architecture", .description = "CPU architecture", + .type = METRIC_FAMILY_TYPE_STRING, .generate = architecture_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "BootID", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "BootID", .description = "Current boot ID", + .type = METRIC_FAMILY_TYPE_STRING, .generate = boot_id_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "Hostname", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Hostname", .description = "System hostname", + .type = METRIC_FAMILY_TYPE_STRING, .generate = hostname_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "KernelVersion", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "KernelVersion", .description = "Kernel version", + .type = METRIC_FAMILY_TYPE_STRING, .generate = kernel_version_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "MachineID", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineID", .description = "Machine ID", + .type = METRIC_FAMILY_TYPE_STRING, .generate = machine_id_generate, }, { - .name = FACT_IO_SYSTEMD_BASIC "Virtualization", + .name = METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization", .description = "Virtualization type", + .type = METRIC_FAMILY_TYPE_STRING, .generate = virtualization_generate, }, {} }; -int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - return facts_method_describe(fact_family_table, link, parameters, flags, userdata); +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(metric_family_table, link, parameters, flags, userdata); } -int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - return facts_method_list(fact_family_table, link, parameters, flags, userdata); +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_list(metric_family_table, link, parameters, flags, userdata); } diff --git a/src/report/report-basic.h b/src/report/report-basic.h index b24613edb6..8f123cb17f 100644 --- a/src/report/report-basic.h +++ b/src/report/report-basic.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -#define FACT_IO_SYSTEMD_BASIC "io.systemd.Basic." +#define METRIC_IO_SYSTEMD_BASIC_PREFIX "io.systemd.Basic." -int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/shared/facts.c b/src/shared/facts.c deleted file mode 100644 index fa16c7b7cb..0000000000 --- a/src/shared/facts.c +++ /dev/null @@ -1,151 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "sd-varlink.h" - -#include "facts.h" -#include "json-util.h" -#include "log.h" -#include "varlink-io.systemd.Facts.h" - -int facts_add_to_varlink_server( - sd_varlink_server *server, - sd_varlink_method_t vl_method_list_cb, - sd_varlink_method_t vl_method_describe_cb) { - - int r; - - assert(server); - assert(vl_method_list_cb); - assert(vl_method_describe_cb); - - r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_Facts); - if (r < 0) - return log_debug_errno(r, "Failed to add varlink facts interface to varlink server: %m"); - - r = sd_varlink_server_bind_method_many( - server, - "io.systemd.Facts.List", vl_method_list_cb, - "io.systemd.Facts.Describe", vl_method_describe_cb); - if (r < 0) - return log_debug_errno(r, "Failed to register varlink facts methods: %m"); - - return 0; -} - -static int fact_family_build_json(const FactFamily *ff, sd_json_variant **ret) { - assert(ff); - - return sd_json_buildo( - ret, - SD_JSON_BUILD_PAIR_STRING("name", ff->name), - SD_JSON_BUILD_PAIR_STRING("description", ff->description)); -} - -int facts_method_describe( - const FactFamily fact_family_table[], - sd_varlink *link, - sd_json_variant *parameters, - sd_varlink_method_flags_t flags, - void *userdata) { - - int r; - - assert(fact_family_table); - assert(link); - assert(parameters); - assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); - - r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); - if (r != 0) - return r; - - r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact"); - if (r < 0) - return r; - - for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - - r = fact_family_build_json(ff, &v); - if (r < 0) - return log_debug_errno(r, "Failed to describe fact family '%s': %m", ff->name); - - r = sd_varlink_reply(link, v); - if (r < 0) - return log_debug_errno(r, "Failed to send varlink reply: %m"); - } - - return 0; -} - -int facts_method_list( - const FactFamily fact_family_table[], - sd_varlink *link, - sd_json_variant *parameters, - sd_varlink_method_flags_t flags, - void *userdata) { - - int r; - - assert(fact_family_table); - assert(link); - assert(parameters); - assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); - - r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); - if (r != 0) - return r; - - r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact"); - if (r < 0) - return r; - - FactFamilyContext ctx = { .link = link }; - for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) { - assert(ff->generate); - - ctx.fact_family = ff; - r = ff->generate(&ctx, userdata); - if (r < 0) - return log_debug_errno( - r, "Failed to list facts for fact family '%s': %m", ff->name); - } - - return 0; -} - -static int fact_build_send(FactFamilyContext *context, const char *object, sd_json_variant *value) { - assert(context); - assert(value); - assert(context->link); - assert(context->fact_family); - - return sd_varlink_replybo(context->link, - SD_JSON_BUILD_PAIR_STRING("name", context->fact_family->name), - JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), - SD_JSON_BUILD_PAIR_VARIANT("value", value)); -} - -int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - int r; - - assert(value); - - r = sd_json_variant_new_string(&v, value); - if (r < 0) - return log_debug_errno(r, "Failed to allocate JSON string: %m"); - - return fact_build_send(context, object, v); -} - -int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - int r; - - r = sd_json_variant_new_unsigned(&v, value); - if (r < 0) - return log_debug_errno(r, "Failed to allocate JSON unsigned: %m"); - - return fact_build_send(context, object, v); -} diff --git a/src/shared/facts.h b/src/shared/facts.h deleted file mode 100644 index 8a8a94cd91..0000000000 --- a/src/shared/facts.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "shared-forward.h" - -typedef struct FactFamily FactFamily; - -typedef struct FactFamilyContext { - const FactFamily *fact_family; - sd_varlink *link; -} FactFamilyContext; - -typedef int (*fact_family_generate_func_t)(FactFamilyContext *ffc, void *userdata); - -typedef struct FactFamily { - const char *name; - const char *description; - fact_family_generate_func_t generate; -} FactFamily; - -/* Add io.systemd.Facts interface + methods to an existing varlink server */ -int facts_add_to_varlink_server( - sd_varlink_server *server, - sd_varlink_method_t vl_method_list_cb, - sd_varlink_method_t vl_method_describe_cb); - -int facts_method_describe(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int facts_method_list(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); - -int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value); -int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value); diff --git a/src/shared/meson.build b/src/shared/meson.build index 84acaf698b..3072bf2dc7 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -80,7 +80,6 @@ shared_sources = files( 'exit-status.c', 'extension-util.c', 'factory-reset.c', - 'facts.c', 'fdisk-util.c', 'fdset.c', 'fido2-util.c', @@ -219,7 +218,6 @@ shared_sources = files( 'varlink-io.systemd.BootControl.c', 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.FactoryReset.c', - 'varlink-io.systemd.Facts.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', 'varlink-io.systemd.InstanceMetadata.c', diff --git a/src/shared/varlink-io.systemd.Facts.c b/src/shared/varlink-io.systemd.Facts.c deleted file mode 100644 index dad1271c72..0000000000 --- a/src/shared/varlink-io.systemd.Facts.c +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "sd-varlink-idl.h" - -#include "varlink-io.systemd.Facts.h" - -static SD_VARLINK_DEFINE_ERROR(NoSuchFact); - -static SD_VARLINK_DEFINE_METHOD_FULL( - List, - SD_VARLINK_REQUIRES_MORE, - SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Basic.Hostname"), - SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), - /* This is currently an unused placeholder. Add examples when we have them. */ - SD_VARLINK_FIELD_COMMENT("Fact object name"), - SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Fact value"), - SD_VARLINK_DEFINE_OUTPUT(value, SD_VARLINK_ANY, 0)); - -static SD_VARLINK_DEFINE_METHOD_FULL( - Describe, - SD_VARLINK_REQUIRES_MORE, - SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Basic.Hostname"), - SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), - SD_VARLINK_FIELD_COMMENT("Fact family description"), - SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, 0)); - -SD_VARLINK_DEFINE_INTERFACE( - io_systemd_Facts, - "io.systemd.Facts", - SD_VARLINK_INTERFACE_COMMENT("Facts APIs"), - SD_VARLINK_SYMBOL_COMMENT("Method to get a list of facts and their values"), - &vl_method_List, - SD_VARLINK_SYMBOL_COMMENT("Method to get the fact families"), - &vl_method_Describe, - SD_VARLINK_SYMBOL_COMMENT("No such fact found"), - &vl_error_NoSuchFact); diff --git a/src/shared/varlink-io.systemd.Facts.h b/src/shared/varlink-io.systemd.Facts.h deleted file mode 100644 index ce07de32fb..0000000000 --- a/src/shared/varlink-io.systemd.Facts.h +++ /dev/null @@ -1,6 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "sd-varlink-idl.h" - -extern const sd_varlink_interface vl_interface_io_systemd_Facts; diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index e9332806b3..321d8b1b79 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -43,8 +43,12 @@ varlinkctl list-methods /run/systemd/report/io.systemd.Network varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {} varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {} -# Make sure the service for "system facts" is enabled +# test io.systemd.Basic Metrics systemctl start systemd-report-basic.socket +varlinkctl info /run/systemd/report/io.systemd.Basic +varlinkctl list-methods /run/systemd/report/io.systemd.Basic +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.Describe {} # Test HTTP upload (plain http) FAKE_SERVER=/usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py diff --git a/units/systemd-report-basic.socket b/units/systemd-report-basic.socket index bfa4ea7256..ba5d88c8e7 100644 --- a/units/systemd-report-basic.socket +++ b/units/systemd-report-basic.socket @@ -7,7 +7,7 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. [Unit] -Description=Report System Basic Facts Socket +Description=Report System Basic Metrics Socket DefaultDependencies=no Before=sockets.target diff --git a/units/systemd-report-basic@.service.in b/units/systemd-report-basic@.service.in index 043324b5c3..1e4798ac04 100644 --- a/units/systemd-report-basic@.service.in +++ b/units/systemd-report-basic@.service.in @@ -7,7 +7,7 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. [Unit] -Description=Report System Basic Facts +Description=Report System Basic Metrics DefaultDependencies=no Conflicts=shutdown.target