report: add simple metrics provider that just reads files
This commit is contained in:
@@ -1185,6 +1185,11 @@ manpages = [
|
||||
['systemd-remount-fs.service', '8', ['systemd-remount-fs'], ''],
|
||||
['systemd-repart', '8', ['systemd-repart.service'], 'ENABLE_REPART'],
|
||||
['systemd-report', '1', [], ''],
|
||||
['systemd-report-files@.service',
|
||||
'8',
|
||||
['systemd-report-files',
|
||||
'systemd-report-files.socket'],
|
||||
''],
|
||||
['systemd-report-sign-plain@.service',
|
||||
'8',
|
||||
['systemd-report-sign-plain',
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0"?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="systemd-report-files_.service" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-report-files@.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-report-files@.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-report-files@.service</refname>
|
||||
<refname>systemd-report-files.socket</refname>
|
||||
<refname>systemd-report-files</refname>
|
||||
<refpurpose>Report the contents of files as system report metrics</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>systemd-report-files@.service</filename></para>
|
||||
<para><filename>systemd-report-files.socket</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-report-files</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-report-files@.service</command> is a metrics source for
|
||||
<citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry>. It
|
||||
reports the contents of a configurable set of files as metrics, by implementing the
|
||||
<constant>io.systemd.Metrics</constant> Varlink interface on a socket linked into the
|
||||
<filename>/run/systemd/report/</filename> directory.</para>
|
||||
|
||||
<para>The files to report are picked up from <filename>report.files/</filename> subdirectories of the
|
||||
usual configuration directories, i.e. from
|
||||
<filename>/etc/systemd/report.files/</filename>,
|
||||
<filename>/run/systemd/report.files/</filename>,
|
||||
<filename>/var/lib/systemd/report.files/</filename>,
|
||||
<filename>/usr/local/lib/systemd/report.files/</filename> and
|
||||
<filename>/usr/lib/systemd/report.files/</filename>. Typically these directories contain symlinks pointing
|
||||
to the actual files in the file system whose contents shall be reported. Entries with the same name in an
|
||||
earlier directory override those in a later one.</para>
|
||||
|
||||
<para>Each entry is reported as a metric named
|
||||
<literal>io.systemd.Files.<replaceable>NAME</replaceable></literal>, where
|
||||
<replaceable>NAME</replaceable> is the name of the entry (which must be a valid metric field name). The
|
||||
metric value is the verbatim contents of the file. Files that cannot be read (for example because a
|
||||
symlink is dangling) are skipped, as are files whose contents are not valid UTF-8 text.</para>
|
||||
|
||||
<para>Note that access to this service is unprivileged, but it runs privileged. This means take care when
|
||||
symlinking files into the various <filename>report.files/</filename> directories: this service might
|
||||
provide read access even if the client does not have the required direct access rights itself.</para>
|
||||
</refsect1>
|
||||
|
||||
<!-- Note: we do not document the command line switches here. The systemd-report-files binary itself should
|
||||
not be invoked by users, but only as a service. Use systemd-report(1) to acquire metrics. -->
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
@@ -33,6 +33,7 @@ enable systemd-nsresourced.socket
|
||||
enable systemd-pstore.service
|
||||
enable systemd-report-basic.socket
|
||||
enable systemd-report-cgroup.socket
|
||||
enable systemd-report-files.socket
|
||||
enable systemd-report-sign-plain.socket
|
||||
enable systemd-resolved.service
|
||||
enable systemd-sysext.service
|
||||
|
||||
@@ -27,6 +27,13 @@ executables += [
|
||||
'report-cgroup-server.c',
|
||||
),
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-report-files',
|
||||
'sources' : files(
|
||||
'report-files.c',
|
||||
'report-files-server.c',
|
||||
),
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-report-sign-plain',
|
||||
'conditions' : [
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "build.h"
|
||||
#include "format-table.h"
|
||||
#include "help-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "report-files.h"
|
||||
#include "varlink-io.systemd.Metrics.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
static int vl_server(void) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL;
|
||||
int r;
|
||||
|
||||
r = varlink_server_new(&vs, /* flags= */ 0, /* userdata= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate Varlink server: %m");
|
||||
|
||||
r = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Metrics);
|
||||
if (r < 0)
|
||||
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)
|
||||
return log_error_errno(r, "Failed to run Varlink event loop: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
help_cmdline("[OPTIONS...]");
|
||||
help_abstract("Report the contents of files as system report metrics.");
|
||||
help_section("Options");
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
help_man_page_reference("systemd-report-files@.service", "8");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
OptionParser opts = { argc, argv };
|
||||
|
||||
FOREACH_OPTION_OR_RETURN(c, &opts)
|
||||
switch (c) {
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
}
|
||||
|
||||
if (option_parser_get_n_args(&opts) > 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"This program takes no arguments.");
|
||||
|
||||
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"This program can only run as a Varlink service.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return vl_server();
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
@@ -0,0 +1,193 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "chase.h"
|
||||
#include "conf-files.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "log.h"
|
||||
#include "metrics.h"
|
||||
#include "path-util.h"
|
||||
#include "report-files.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "utf8.h"
|
||||
#include "varlink-idl-util.h"
|
||||
|
||||
/* Upper bounds, to protect against pathologically large directories or files. */
|
||||
#define REPORT_FILES_MAX 1024U
|
||||
#define REPORT_FILE_SIZE_MAX (4U * 1024U * 1024U)
|
||||
|
||||
/* The directories we look for files to report in. This is the usual CONF_PATHS() set (/etc/, /run/,
|
||||
* /usr/local/lib/, /usr/lib/), plus an extra directory below /var/lib/ for persistent local additions. Files
|
||||
* (typically symlinks to the actual files to report) dropped into any of these are reported as metrics, keyed
|
||||
* by their name. Entries in earlier directories override identically named ones in later directories. */
|
||||
static const char* const report_files_dirs[] = {
|
||||
"/etc/systemd/report.files",
|
||||
"/run/systemd/report.files",
|
||||
"/var/lib/systemd/report.files",
|
||||
"/usr/local/lib/systemd/report.files",
|
||||
"/usr/lib/systemd/report.files",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static MetricFamily* metric_family_array_free(MetricFamily *families) {
|
||||
if (!families)
|
||||
return NULL;
|
||||
|
||||
/* The array is NULL-name terminated. We own the name/description strings. */
|
||||
for (MetricFamily *mf = families; mf->name; mf++) {
|
||||
free((char*) mf->name);
|
||||
free((char*) mf->description);
|
||||
}
|
||||
|
||||
return mfree(families);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(MetricFamily*, metric_family_array_free);
|
||||
|
||||
static int file_metric_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
|
||||
int r;
|
||||
|
||||
assert(mf && mf->name);
|
||||
assert(link);
|
||||
|
||||
/* Recover the file name from the metric family name: it is the part following the interface prefix.
|
||||
* We look it up across our directories again (rather than caching the path found while building the
|
||||
* metric list), and read the first instance we find, matching the precedence used at enumeration. */
|
||||
const char *field = startswith(mf->name, METRIC_IO_SYSTEMD_FILES_PREFIX);
|
||||
assert(field);
|
||||
|
||||
_cleanup_free_ char *buf = NULL, *discovered_path = NULL;
|
||||
size_t size = 0;
|
||||
STRV_FOREACH(d, report_files_dirs) {
|
||||
_cleanup_free_ char *path = path_join(*d, field);
|
||||
if (!path)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
_cleanup_close_ int fd = chase_and_open(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, &resolved);
|
||||
if (fd == -ENOENT) /* Not in this directory (or dangling symlink): try the next one. */
|
||||
continue;
|
||||
if (fd < 0) {
|
||||
log_warning_errno(fd, "Failed to open '%s', skipping: %m", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = read_full_file_full(
|
||||
fd,
|
||||
/* filename= */ NULL,
|
||||
/* offset= */ UINT64_MAX,
|
||||
REPORT_FILE_SIZE_MAX,
|
||||
READ_FULL_FILE_FAIL_WHEN_LARGER,
|
||||
/* bind_name= */ NULL,
|
||||
&buf,
|
||||
&size);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to read '%s', skipping: %m", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
discovered_path = TAKE_PTR(resolved);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!discovered_path) {
|
||||
log_debug("File for metric '%s' unavailable, skipping.", mf->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Metric values are JSON strings, so we can only report text files. Skip anything that isn't valid,
|
||||
* NUL-free UTF-8. */
|
||||
if (memchr(buf, 0, size) || !utf8_is_valid(buf)) {
|
||||
log_debug("File for metric '%s' is not valid UTF-8 text, skipping.", mf->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return metric_build_send_string(mf, link, discovered_path, buf, /* fields= */ NULL);
|
||||
}
|
||||
|
||||
static int build_file_metrics(MetricFamily **ret) {
|
||||
_cleanup_(metric_family_array_freep) MetricFamily *families = NULL;
|
||||
_cleanup_strv_free_ char **files = NULL;
|
||||
size_t n = 0;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
/* Enumerate the files to report across all our directories, deduplicated by name. The entry name is
|
||||
* used as the metric field name. */
|
||||
r = conf_files_list_strv(&files, /* suffix= */ NULL, /* root= */ NULL, CONF_FILES_REGULAR, report_files_dirs);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate report files: %m");
|
||||
|
||||
STRV_FOREACH(f, files) {
|
||||
_cleanup_free_ char *base = NULL;
|
||||
r = path_extract_filename(*f, &base);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract file name from '%s': %m", *f);
|
||||
|
||||
/* The name becomes the metric field name, so it must be a valid one. */
|
||||
if (!varlink_idl_field_name_is_valid(base)) {
|
||||
log_debug("Report file '%s' does not have a valid metric field name, skipping.", *f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n >= REPORT_FILES_MAX) {
|
||||
log_warning("More than %u report files found, not reporting the rest.", REPORT_FILES_MAX);
|
||||
break;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *name = strjoin(METRIC_IO_SYSTEMD_FILES_PREFIX, base);
|
||||
_cleanup_free_ char *description = strjoin("Contents of the '", base, "' report file");
|
||||
if (!name || !description)
|
||||
return log_oom();
|
||||
|
||||
/* Room for the new entry plus the NULL-name terminator. */
|
||||
if (!GREEDY_REALLOC(families, n + 2))
|
||||
return log_oom();
|
||||
|
||||
families[n++] = (MetricFamily) {
|
||||
.name = TAKE_PTR(name),
|
||||
.description = TAKE_PTR(description),
|
||||
.type = METRIC_FAMILY_TYPE_STRING,
|
||||
.generate = file_metric_generate,
|
||||
};
|
||||
families[n] = (MetricFamily) {}; /* terminator */
|
||||
}
|
||||
|
||||
/* The metrics helpers expect a valid, terminated array even when empty. */
|
||||
if (!families) {
|
||||
families = new0(MetricFamily, 1);
|
||||
if (!families)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(families);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
int r;
|
||||
|
||||
_cleanup_(metric_family_array_freep) MetricFamily *families = NULL;
|
||||
r = build_file_metrics(&families);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return metrics_method_list(families, link, parameters, flags, /* userdata= */ NULL);
|
||||
}
|
||||
|
||||
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
int r;
|
||||
|
||||
_cleanup_(metric_family_array_freep) MetricFamily *families = NULL;
|
||||
r = build_file_metrics(&families);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return metrics_method_describe(families, link, parameters, flags, /* userdata= */ NULL);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "shared-forward.h"
|
||||
|
||||
#define METRIC_IO_SYSTEMD_FILES_PREFIX "io.systemd.Files."
|
||||
|
||||
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);
|
||||
@@ -731,6 +731,8 @@ units = [
|
||||
},
|
||||
{ 'file' : 'systemd-report-cgroup.socket' },
|
||||
{ 'file' : 'systemd-report-cgroup@.service.in' },
|
||||
{ 'file' : 'systemd-report-files.socket' },
|
||||
{ 'file' : 'systemd-report-files@.service.in' },
|
||||
{
|
||||
'file' : 'systemd-report-sign-plain.socket',
|
||||
'conditions' : ['HAVE_OPENSSL'],
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Report Files Metrics Socket
|
||||
Documentation=man:systemd-report-files@.service(8)
|
||||
DefaultDependencies=no
|
||||
Before=sockets.target shutdown.target
|
||||
Conflicts=shutdown.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/report/io.systemd.Files
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0666
|
||||
Accept=yes
|
||||
MaxConnectionsPerSource=16
|
||||
RemoveOnStop=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Report Files Metrics
|
||||
Documentation=man:systemd-report-files@.service(8)
|
||||
DefaultDependencies=no
|
||||
Conflicts=shutdown.target
|
||||
Before=shutdown.target
|
||||
|
||||
[Service]
|
||||
ExecStart={{LIBEXECDIR}}/systemd-report-files
|
||||
Reference in New Issue
Block a user