report-basic: add a bunch more essential metrics (#42248)

This commit is contained in:
Luca Boccassi
2026-05-22 13:29:08 +01:00
committed by GitHub
2 changed files with 337 additions and 0 deletions
+297
View File
@@ -1,20 +1,29 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <sys/utsname.h>
#include "sd-device.h"
#include "sd-id128.h"
#include "sd-json.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "architecture.h"
#include "confidential-virt.h"
#include "cpu-set-util.h"
#include "env-file.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hostname-setup.h"
#include "hostname-util.h"
#include "limits-util.h"
#include "log.h"
#include "metrics.h"
#include "os-util.h"
#include "report-basic.h"
#include "string-util.h"
#include "virt.h"
static int architecture_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
@@ -198,6 +207,221 @@ static int os_release_generate(const MetricFamily mf[static _FIELD_MAX - 1], sd_
return 0;
}
static int machine_info_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
enum {
MACHINE_INFO_FIELD_PRETTY_HOSTNAME,
MACHINE_INFO_FIELD_DEPLOYMENT,
MACHINE_INFO_FIELD_LOCATION,
MACHINE_INFO_FIELD_TAGS,
_MACHINE_INFO_FIELD_MAX,
};
char* values[_MACHINE_INFO_FIELD_MAX] = {};
CLEANUP_ELEMENTS(values, free_many_charp);
int r;
assert(mf && mf->name);
assert(link);
r = parse_env_file(/* f= */ NULL, etc_machine_info(),
"PRETTY_HOSTNAME", &values[MACHINE_INFO_FIELD_PRETTY_HOSTNAME],
"DEPLOYMENT", &values[MACHINE_INFO_FIELD_DEPLOYMENT],
"LOCATION", &values[MACHINE_INFO_FIELD_LOCATION],
"TAGS", &values[MACHINE_INFO_FIELD_TAGS]);
if (r < 0) {
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read machine-info file, ignoring: %m");
return 0;
}
for (size_t i = 0; i < _MACHINE_INFO_FIELD_MAX; i++) {
const char *v = values[i];
if (!v)
continue;
r = metric_build_send_string(
mf + i,
link,
/* object= */ NULL,
v,
/* fields= */ NULL);
if (r < 0)
return r;
}
return 0;
}
static int smbios_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
enum {
SMBIOS_FIELD_SYS_VENDOR,
SMBIOS_FIELD_PRODUCT_NAME,
SMBIOS_FIELD_PRODUCT_VERSION,
SMBIOS_FIELD_PRODUCT_SKU,
SMBIOS_FIELD_PRODUCT_FAMILY,
SMBIOS_FIELD_PRODUCT_SERIAL,
SMBIOS_FIELD_PRODUCT_UUID,
SMBIOS_FIELD_BOARD_VENDOR,
SMBIOS_FIELD_BOARD_NAME,
SMBIOS_FIELD_BOARD_VERSION,
SMBIOS_FIELD_BOARD_SERIAL,
SMBIOS_FIELD_BOARD_ASSET_TAG,
SMBIOS_FIELD_BIOS_VENDOR,
SMBIOS_FIELD_BIOS_VERSION,
SMBIOS_FIELD_BIOS_DATE,
SMBIOS_FIELD_CHASSIS_TYPE,
SMBIOS_FIELD_CHASSIS_VENDOR,
SMBIOS_FIELD_CHASSIS_SERIAL,
SMBIOS_FIELD_CHASSIS_ASSET_TAG,
_SMBIOS_FIELD_MAX,
};
/* The sysfs attribute names exposed by the kernel below /sys/class/dmi/id/. The order must match the
* SMBIOS_STANDARD_FIELD() entries in the metric family table below. */
static const char* const smbios_files[_SMBIOS_FIELD_MAX] = {
/* SMBIOS Type 1 */
[SMBIOS_FIELD_SYS_VENDOR] = "sys_vendor",
[SMBIOS_FIELD_PRODUCT_NAME] = "product_name",
[SMBIOS_FIELD_PRODUCT_VERSION] = "product_version",
[SMBIOS_FIELD_PRODUCT_SKU] = "product_sku",
[SMBIOS_FIELD_PRODUCT_FAMILY] = "product_family",
[SMBIOS_FIELD_PRODUCT_SERIAL] = "product_serial",
[SMBIOS_FIELD_PRODUCT_UUID] = "product_uuid",
/* SMBIOS Type 2 */
[SMBIOS_FIELD_BOARD_VENDOR] = "board_vendor",
[SMBIOS_FIELD_BOARD_NAME] = "board_name",
[SMBIOS_FIELD_BOARD_VERSION] = "board_version",
[SMBIOS_FIELD_BOARD_SERIAL] = "board_serial",
[SMBIOS_FIELD_BOARD_ASSET_TAG] = "board_asset_tag",
/* SMBIOS Type 0 */
[SMBIOS_FIELD_BIOS_VENDOR] = "bios_vendor",
[SMBIOS_FIELD_BIOS_VERSION] = "bios_version",
[SMBIOS_FIELD_BIOS_DATE] = "bios_date",
/* SMBIOS Type 3 */
[SMBIOS_FIELD_CHASSIS_TYPE] = "chassis_type",
[SMBIOS_FIELD_CHASSIS_VENDOR] = "chassis_vendor",
[SMBIOS_FIELD_CHASSIS_SERIAL] = "chassis_serial",
[SMBIOS_FIELD_CHASSIS_ASSET_TAG] = "chassis_asset_tag",
};
int r;
assert(mf && mf->name);
assert(link);
/* Reports the fundamental SMBIOS/DMI identification fields. Some of these (serial numbers, asset
* tags, the system UUID) are privacy sensitive and only readable by root — if we lack the
* privileges to read them we simply skip them. */
_cleanup_close_ int dir_fd = open("/sys/class/dmi/id", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
if (dir_fd < 0) {
log_full_errno(ERRNO_IS_DEVICE_ABSENT(errno) ? LOG_DEBUG : LOG_WARNING, errno,
"Failed to open /sys/class/dmi/id/, ignoring: %m");
return 0;
}
for (size_t i = 0; i < _SMBIOS_FIELD_MAX; i++) {
_cleanup_free_ char *buf = NULL;
r = read_virtual_file_at(dir_fd, smbios_files[i], /* max_size= */ SIZE_MAX, &buf, /* ret_size= */ NULL);
if (r < 0) {
log_full_errno(r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read SMBIOS field '%s', ignoring: %m", smbios_files[i]);
continue;
}
delete_trailing_chars(buf, NEWLINE);
if (isempty(buf))
continue;
if (!string_is_safe(buf, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) {
log_debug("SMBIOS field '%s' contains unsafe characters, ignoring.", smbios_files[i]);
continue;
}
r = metric_build_send_string(
mf + i,
link,
/* object= */ NULL,
buf,
/* fields= */ NULL);
if (r < 0)
return r;
}
return 0;
}
static int tpm2_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
enum {
TPM2_FIELD_MANUFACTURER,
TPM2_FIELD_VENDOR_STRING,
_TPM2_FIELD_MAX,
};
/* The udev properties set by the 'tpm2_id' builtin on the tpmrm device. The order must match the
* metric family table entries below. */
static const char* const tpm2_properties[_TPM2_FIELD_MAX] = {
[TPM2_FIELD_MANUFACTURER] = "ID_TPM2_MANUFACTURER",
[TPM2_FIELD_VENDOR_STRING] = "ID_TPM2_VENDOR_STRING",
};
int r;
assert(mf && mf->name);
assert(link);
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
r = sd_device_new_from_subsystem_sysname(&dev, "tpmrm", "tpmrm0");
if (r < 0) {
log_full_errno(ERRNO_IS_NEG_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_WARNING, r,
"Failed to open tpmrm0 device, ignoring: %m");
return 0;
}
for (size_t i = 0; i < _TPM2_FIELD_MAX; i++) {
const char *v;
r = sd_device_get_property_value(dev, tpm2_properties[i], &v);
if (r < 0) {
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read TPM2 property '%s', ignoring: %m", tpm2_properties[i]);
continue;
}
if (isempty(v))
continue;
r = metric_build_send_string(
mf + i,
link,
/* object= */ NULL,
v,
/* fields= */ NULL);
if (r < 0)
return r;
}
return 0;
}
static int confidential_virtualization_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
assert(mf && mf->name);
assert(link);
ConfidentialVirtualization cv = detect_confidential_virtualization();
if (cv < 0)
return cv;
return metric_build_send_string(
mf,
link,
/* object= */ NULL,
confidential_virtualization_to_string(cv),
/* fields= */ NULL);
}
static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
assert(mf && mf->name);
assert(link);
@@ -222,6 +446,22 @@ static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, voi
.generate = NULL, \
}
#define MACHINE_INFO_STANDARD_FIELD(name) \
{ \
METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineInfo." name, \
"Machine identification (" name "= field from machine-info)", \
METRIC_FAMILY_TYPE_STRING, \
.generate = NULL, \
}
#define SMBIOS_STANDARD_FIELD(name) \
{ \
METRIC_IO_SYSTEMD_BASIC_PREFIX "SMBIOS." name, \
"Firmware/hardware identification (" name " field from SMBIOS/DMI)", \
METRIC_FAMILY_TYPE_STRING, \
.generate = NULL, \
}
static const MetricFamily metric_family_table[] = {
/* Keep entries ordered alphabetically */
{
@@ -236,6 +476,12 @@ static const MetricFamily metric_family_table[] = {
METRIC_FAMILY_TYPE_STRING,
.generate = boot_id_generate,
},
{
METRIC_IO_SYSTEMD_BASIC_PREFIX "ConfidentialVirtualization",
"Confidential computing technology",
METRIC_FAMILY_TYPE_STRING,
.generate = confidential_virtualization_generate,
},
{
METRIC_IO_SYSTEMD_BASIC_PREFIX "CPUsOnline",
"Number of CPUs currently online",
@@ -260,6 +506,16 @@ static const MetricFamily metric_family_table[] = {
METRIC_FAMILY_TYPE_STRING,
.generate = machine_id_generate,
},
{
METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineInfo.PRETTY_HOSTNAME",
"Pretty hostname (PRETTY_HOSTNAME= field from machine-info)",
METRIC_FAMILY_TYPE_STRING,
.generate = machine_info_generate,
},
MACHINE_INFO_STANDARD_FIELD("DEPLOYMENT"),
MACHINE_INFO_STANDARD_FIELD("LOCATION"),
MACHINE_INFO_STANDARD_FIELD("TAGS"),
/* Keep those ↑ in sync with machine_info_generate(). */
{
METRIC_IO_SYSTEMD_BASIC_PREFIX "OSRelease.NAME",
"Operating system human-readable name (PRETTY_NAME= or NAME= field from os-release)",
@@ -284,6 +540,47 @@ static const MetricFamily metric_family_table[] = {
METRIC_FAMILY_TYPE_GAUGE,
.generate = physical_memory_generate,
},
{
/* NB: Here we use the naming of the field as per SMBIOS specification, i.e. undo the weird
* renaming that Linux did on the fields. When new fields are added here, please make sure to
* check the specification again for naming them. */
METRIC_IO_SYSTEMD_BASIC_PREFIX "SMBIOS.SystemManufacturer",
"Firmware/hardware identification (SystemManufacturer field from SMBIOS/DMI)",
METRIC_FAMILY_TYPE_STRING,
.generate = smbios_generate,
},
SMBIOS_STANDARD_FIELD("SystemProductName"),
SMBIOS_STANDARD_FIELD("SystemVersion"),
SMBIOS_STANDARD_FIELD("SystemSKUNumber"),
SMBIOS_STANDARD_FIELD("SystemFamily"),
SMBIOS_STANDARD_FIELD("SystemSerialNumber"),
SMBIOS_STANDARD_FIELD("SystemUUID"),
SMBIOS_STANDARD_FIELD("BaseBoardManufacturer"),
SMBIOS_STANDARD_FIELD("BaseBoardProduct"),
SMBIOS_STANDARD_FIELD("BaseBoardVersion"),
SMBIOS_STANDARD_FIELD("BaseBoardSerial"),
SMBIOS_STANDARD_FIELD("BaseBoardAssetTag"),
SMBIOS_STANDARD_FIELD("FirmwareVendor"),
SMBIOS_STANDARD_FIELD("FirmwareVersion"),
SMBIOS_STANDARD_FIELD("FirmwareReleaseDate"),
SMBIOS_STANDARD_FIELD("ChassisType"),
SMBIOS_STANDARD_FIELD("ChassisManufacturer"),
SMBIOS_STANDARD_FIELD("ChassisSerialNumber"),
SMBIOS_STANDARD_FIELD("ChassisAssetTagNumber"),
/* Keep those ↑ in sync with smbios_generate(). */
{
METRIC_IO_SYSTEMD_BASIC_PREFIX "TPM2.Manufacturer",
"TPM2 device manufacturer (ID_TPM2_MANUFACTURER property of the tpmrm0 device)",
METRIC_FAMILY_TYPE_STRING,
.generate = tpm2_generate,
},
{
METRIC_IO_SYSTEMD_BASIC_PREFIX "TPM2.VendorString",
"TPM2 device vendor string (ID_TPM2_VENDOR_STRING property of the tpmrm0 device)",
METRIC_FAMILY_TYPE_STRING,
.generate = NULL,
},
/* Keep those ↑ in sync with tpm2_generate(). */
{
METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization",
"Virtualization type",
+40
View File
@@ -62,6 +62,46 @@ id1="$(varlinkctl call --more /run/systemd/report/io.systemd.Basic io.systemd.Me
id2="$(. /etc/os-release; echo "$ID")"
[ "$id1" = "$id2" ]
# test io.systemd.Basic.MachineInfo.* metrics, sourced from /etc/machine-info
if [ -e /etc/machine-info ]; then
MACHINE_INFO_BACKUP="$(mktemp)"
cp /etc/machine-info "$MACHINE_INFO_BACKUP"
MACHINE_INFO_EXISTED=1
else
MACHINE_INFO_EXISTED=0
fi
restore_machine_info() {
set +e
if [ "$MACHINE_INFO_EXISTED" = 1 ]; then
cp "$MACHINE_INFO_BACKUP" /etc/machine-info
rm -f "$MACHINE_INFO_BACKUP"
else
rm -f /etc/machine-info
fi
set -e
}
trap restore_machine_info EXIT
cat >/etc/machine-info <<EOF
PRETTY_HOSTNAME="Test Machine"
DEPLOYMENT=test
LOCATION="Test Lab, Rack 3"
TAGS=foo:bar:baz
EOF
# The metric source re-reads /etc/machine-info on every call, so no service restart is needed.
machine_info_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {})"
machine_info_value() { echo "$machine_info_metrics" | jq --seq -r "select(.name == \"$1\") | .value"; }
[ "$(machine_info_value io.systemd.Basic.MachineInfo.PRETTY_HOSTNAME)" = "Test Machine" ]
[ "$(machine_info_value io.systemd.Basic.MachineInfo.DEPLOYMENT)" = "test" ]
[ "$(machine_info_value io.systemd.Basic.MachineInfo.LOCATION)" = "Test Lab, Rack 3" ]
[ "$(machine_info_value io.systemd.Basic.MachineInfo.TAGS)" = "foo:bar:baz" ]
restore_machine_info
trap - EXIT
# 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)