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'