Merge facts into metrics and add systemd-report generate/upload (#41688)
This commit is contained in:
+103
-7
@@ -1,6 +1,9 @@
|
||||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
|
||||
<!ENTITY % entities SYSTEM "custom-entities.ent" >
|
||||
%entities;
|
||||
]>
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="systemd-report"
|
||||
@@ -18,7 +21,7 @@
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-report</refname>
|
||||
<refpurpose>Generate report of system facts and metrics</refpurpose>
|
||||
<refpurpose>Generate report of system metrics</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
@@ -33,7 +36,7 @@
|
||||
<para><emphasis>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.</emphasis></para>
|
||||
|
||||
<para><command>systemd-report</command> requests facts and metrics from the system and writes them to
|
||||
<para><command>systemd-report</command> requests metrics from the system and writes them to
|
||||
standard output.</para>
|
||||
</refsect1>
|
||||
|
||||
@@ -56,18 +59,43 @@
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>describe-metrics</command> <arg choice="opt" rep="repeat">MATCH</arg></term>
|
||||
<term><command>describe</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 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>
|
||||
<para>Match expressions supported by <command>metrics</command> are supported by
|
||||
<command>describe</command> too.</para>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -101,6 +129,74 @@
|
||||
<xi:include href="standard-options.xml" xpointer="no-legend" />
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--url=<replaceable>URL</replaceable></option></term>
|
||||
|
||||
<listitem><para>Upload the collected report to the specified address instead of writing it to
|
||||
standard output. <replaceable>URL</replaceable> must point to a server accepting POST requests with
|
||||
a JSON-formatted report body.</para>
|
||||
|
||||
<para>Note: both <literal>http://</literal> and <literal>https://</literal> URLs are supported, but
|
||||
<emphasis>connections over plain HTTP are made without encryption</emphasis>. 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 <literal>https://</literal> is recommended.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--key=<replaceable>FILENAME</replaceable></option></term>
|
||||
|
||||
<listitem><para>Takes a path to a SSL key file in PEM format, used for client certificate
|
||||
authentication when uploading. Can also be set to <literal>-</literal>, to disable client certificate
|
||||
authentication. Defaults to
|
||||
<filename>&CERTIFICATE_ROOT;/private/systemd-report.pem</filename>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--cert=<replaceable>FILENAME</replaceable></option></term>
|
||||
|
||||
<listitem><para>Takes a path to a SSL certificate file in PEM format, used for client certificate
|
||||
authentication when uploading. Defaults to
|
||||
<filename>&CERTIFICATE_ROOT;/certs/systemd-report.pem</filename>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--trust=<replaceable>FILENAME</replaceable>|<constant>all</constant></option></term>
|
||||
|
||||
<listitem><para>Takes a path to a SSL CA certificate file in PEM format used to verify the server
|
||||
certificate, or the literal string <constant>all</constant> to disable certificate checking
|
||||
entirely. Defaults to <filename>&CERTIFICATE_ROOT;/ca/trusted.pem</filename>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--network-timeout=<replaceable>SEC</replaceable></option></term>
|
||||
|
||||
<listitem><para>Timeout for the network upload operation. Takes a value in seconds (or in other
|
||||
time units if suffixed with <literal>ms</literal>, <literal>min</literal>, <literal>h</literal>,
|
||||
etc.); see
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
for details. Defaults to 30 seconds.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--extra-header=<replaceable>NAME</replaceable>: <replaceable>VALUE</replaceable></option></term>
|
||||
|
||||
<listitem><para>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
|
||||
<option>--extra-header=</option> uses.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
</refsect1>
|
||||
|
||||
@@ -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)
|
||||
|
||||
+43
-31
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+96
-23
@@ -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. */
|
||||
@@ -59,26 +63,18 @@ 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_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(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");
|
||||
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] = {};
|
||||
@@ -89,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",
|
||||
@@ -214,3 +202,88 @@ 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);
|
||||
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: ");
|
||||
// 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;
|
||||
}
|
||||
|
||||
+29
-230
@@ -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"
|
||||
@@ -28,15 +27,15 @@
|
||||
#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;
|
||||
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,15 +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",
|
||||
[ACTION_LIST_FACTS] = "io.systemd.Facts.List",
|
||||
[ACTION_DESCRIBE_FACTS] = "io.systemd.Facts.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;
|
||||
@@ -249,7 +239,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;
|
||||
}
|
||||
@@ -290,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");
|
||||
@@ -302,7 +296,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)
|
||||
@@ -436,107 +432,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 +450,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 +467,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 +480,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,18 +587,24 @@ 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");
|
||||
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)
|
||||
@@ -746,7 +632,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;
|
||||
}
|
||||
@@ -769,8 +655,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)
|
||||
@@ -792,93 +678,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 +754,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(),
|
||||
|
||||
+10
-6
@@ -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,21 +19,22 @@ extern usec_t arg_network_timeout_usec;
|
||||
typedef enum Action {
|
||||
ACTION_LIST_METRICS,
|
||||
ACTION_DESCRIBE_METRICS,
|
||||
ACTION_LIST_FACTS,
|
||||
ACTION_DESCRIBE_FACTS,
|
||||
ACTION_GENERATE,
|
||||
ACTION_UPLOAD,
|
||||
_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;
|
||||
|
||||
int upload_result;
|
||||
struct iovec_wrapper upload_answer;
|
||||
} Context;
|
||||
|
||||
int upload_collected(Context *context);
|
||||
int report_collected(Context *context);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
+2
-2
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -50,28 +50,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
|
||||
|
||||
# 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 {}
|
||||
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
|
||||
@@ -87,8 +71,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" facts --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" \
|
||||
@@ -98,6 +85,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" upload --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \
|
||||
--extra-header='Authorization: Bearer magic string'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user