<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><command>generate</command> <arg choice="opt" rep="repeat">MATCH</arg></term>
+
+ <listitem><para>Acquire a list of metrics and build a JSON report.</para>
+
+ <para>Match expressions supported by <command>metrics</command> are supported here too.</para>
+
+ <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><command>upload</command> <arg choice="opt" rep="repeat">MATCH</arg></term>
+
+ <listitem><para>This command can be used to send the report built by <command>generate</command>
+ to an external server. Two upload mechanisms are supported. If an <literal>http://</literal> or
+ <literal>https://</literal> URL is specified with <option>--url=</option>, an HTTP upload will be
+ performed to the specified location. Otherwise, any sockets under
+ <filename>/run/systemd/metrics-upload/</filename> will be used to call
+ <function>io.systemd.Report.Upload()</function>.</para>
+
+ <para>Match expressions supported by <command>metrics</command> are supported here too.</para>
+
+ <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><command>list-sources</command></term>
#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
return nmemb;
}
+#endif
static int build_json_report(Context *context, sd_json_variant **ret) {
/* Convert the variant array to a JSON report. */
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",
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] = {};
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. */
-
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL;
- r = build_json_report(context, &vl);
- if (r < 0)
- return r;
+ /* Upload a JSON report in text form as a single JSON object, instead of a JSON-SEQ list. */
- 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",
"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;
+}
#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"
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;
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;
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)
"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)
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)
#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;
typedef enum Action {
ACTION_LIST_METRICS,
ACTION_DESCRIBE_METRICS,
+ ACTION_GENERATE,
+ ACTION_UPLOAD,
_ACTION_MAX,
_ACTION_INVALID = -EINVAL,
} Action;
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);
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" \
"$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'