]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
report-basic: also report load average + swap size in metrics report
authorLennart Poettering <lennart@amutable.com>
Mon, 15 Jun 2026 08:19:54 +0000 (10:19 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 19 Jun 2026 18:37:57 +0000 (20:37 +0200)
src/report/report-basic.c
src/shared/metrics.c
src/shared/metrics.h
test/units/TEST-74-AUX-UTILS.report.sh

index cf7a4f0b5ef4b063149b46a38ce156928872099a..56f7065c68d316752fae73fd616980e06184456b 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <fcntl.h>
+#include <sys/sysinfo.h>
 #include <sys/utsname.h>
 
 #include "sd-device.h"
@@ -142,6 +143,67 @@ static int cpus_online_generate(const MetricFamily *mf, sd_varlink *link, void *
                         /* fields= */ NULL);
 }
 
+static int load_average_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
+        enum {
+                LOAD_AVERAGE_FIELD_1MIN,
+                LOAD_AVERAGE_FIELD_5MIN,
+                LOAD_AVERAGE_FIELD_15MIN,
+                _LOAD_AVERAGE_FIELD_MAX,
+        };
+
+        int r;
+
+        assert(mf && mf->name);
+        assert(link);
+
+        /* The classic Linux load average, i.e. the exponentially damped moving average of the number of
+         * runnable plus uninterruptible tasks over the last 1, 5 and 15 minutes. The kernel exposes these as
+         * fixed-point numbers shifted left by SI_LOAD_SHIFT bits. */
+
+        struct sysinfo info;
+        if (sysinfo(&info) < 0)
+                return log_debug_errno(errno, "Failed to call sysinfo(): %m");
+
+        assert_cc(_LOAD_AVERAGE_FIELD_MAX == ELEMENTSOF(info.loads));
+
+        for (size_t i = 0; i < _LOAD_AVERAGE_FIELD_MAX; i++) {
+
+                r = metric_build_send_double(
+                                mf + i,
+                                link,
+                                /* object= */ NULL,
+                                (double) info.loads[i] / (UINT64_C(1) << SI_LOAD_SHIFT),
+                                /* fields= */ NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int swap_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
+        assert(mf && mf->name);
+        assert(link);
+
+        /* The total amount of configured swap space, in bytes. */
+
+        struct sysinfo info;
+        if (sysinfo(&info) < 0)
+                return log_debug_errno(errno, "Failed to call sysinfo(): %m");
+
+        /* Overflow is unrealistic (would need >16 EiB of swap), but use MUL_SAFE to make this obvious to
+         * static analyzers. */
+        uint64_t swap;
+        assert_se(MUL_SAFE(&swap, (uint64_t) info.totalswap, (uint64_t) info.mem_unit));
+
+        return metric_build_send_unsigned(
+                        mf,
+                        link,
+                        /* object= */ NULL,
+                        swap,
+                        /* fields= */ NULL);
+}
+
 enum {
         FIELD_PRETTY_NAME,
         FIELD_NAME,
@@ -500,6 +562,25 @@ static const MetricFamily metric_family_table[] = {
                 METRIC_FAMILY_TYPE_STRING,
                 .generate = kernel_version_generate,
         },
+        {
+                METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage1Min",
+                "System load average over the last 1 minute",
+                METRIC_FAMILY_TYPE_GAUGE,
+                .generate = load_average_generate,
+        },
+        {
+                METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage5Min",
+                "System load average over the last 5 minutes",
+                METRIC_FAMILY_TYPE_GAUGE,
+                .generate = NULL,
+        },
+        {
+                METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage15Min",
+                "System load average over the last 15 minutes",
+                METRIC_FAMILY_TYPE_GAUGE,
+                .generate = NULL,
+        },
+        /* Keep those ↑ in sync with load_average_generate(). */
         {
                 METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineID",
                 "Machine ID",
@@ -568,6 +649,12 @@ static const MetricFamily metric_family_table[] = {
         SMBIOS_STANDARD_FIELD("ChassisSerialNumber"),
         SMBIOS_STANDARD_FIELD("ChassisAssetTagNumber"),
         /* Keep those ↑ in sync with smbios_generate(). */
+        {
+                METRIC_IO_SYSTEMD_BASIC_PREFIX "SwapBytes",
+                "Total configured swap space in bytes",
+                METRIC_FAMILY_TYPE_GAUGE,
+                .generate = swap_generate,
+        },
         {
                 METRIC_IO_SYSTEMD_BASIC_PREFIX "TPM2.Manufacturer",
                 "TPM2 device manufacturer (ID_TPM2_MANUFACTURER property of the tpmrm0 device)",
index 7c50f8ab34ee6a16d8bd1205debb7e8f61fabd8f..9a587f32dbb63321ecd46ea3a22f9a96b0d920de 100644 (file)
@@ -209,3 +209,23 @@ int metric_build_send_unsigned(
 
         return metric_build_send(mf, link, object, v, fields);
 }
+
+int metric_build_send_double(
+                const MetricFamily *mf,
+                sd_varlink *link,
+                const char *object,
+                double value,
+                sd_json_variant *fields) {
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        int r;
+
+        assert(mf);
+        assert(link);
+
+        r = sd_json_variant_new_real(&v, value);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to allocate JSON real: %m");
+
+        return metric_build_send(mf, link, object, v, fields);
+}
index 7c4afb5de77db83419701cf9f2ba03a529c1ba6f..63b1946450469a5a65e429db0002d20ef1548354 100644 (file)
@@ -38,3 +38,4 @@ int metrics_method_list(const MetricFamily mfs[], sd_varlink *link, sd_json_vari
 
 int metric_build_send_string(const MetricFamily* mf, sd_varlink *link, const char *object, const char *value, sd_json_variant *fields);
 int metric_build_send_unsigned(const MetricFamily* mf, sd_varlink *link, const char *object, uint64_t value, sd_json_variant *fields);
+int metric_build_send_double(const MetricFamily* mf, sd_varlink *link, const char *object, double value, sd_json_variant *fields);
index f758d24030db5848756494ebc7aadf4d6f5a4c18..4b733ad611fc87243c789bf2c7d49ceef8357985 100755 (executable)
@@ -62,6 +62,24 @@ 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 load average and swap metrics
+basic_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {})"
+# NB: '| tostring' turns the numeric value into a raw string, so 'jq -r' prints it cleanly
+# (numbers, unlike strings, are otherwise still emitted with the json-seq record separator).
+basic_value() { echo "$basic_metrics" | jq --seq -r "select(.name == \"$1\") | .value | tostring"; }
+
+# The three classic load average fields must be present and numeric (jq's 'numbers' filter
+# emits the value only if it is a JSON number, so a non-empty result confirms both).
+for field in LoadAverage1Min LoadAverage5Min LoadAverage15Min; do
+    loadavg="$(echo "$basic_metrics" | jq --seq -r "select(.name == \"io.systemd.Basic.$field\") | .value | numbers | tostring")"
+    test -n "$loadavg"
+done
+
+# SwapBytes must match the total the kernel reports in /proc/meminfo (which is in kB).
+swap_reported="$(basic_value io.systemd.Basic.SwapBytes)"
+swap_expected=$(( $(awk '/^SwapTotal:/ { print $2; found=1 } END { if (!found) print 0 }' /proc/meminfo) * 1024 ))
+[ "$swap_reported" = "$swap_expected" ]
+
 # test io.systemd.Basic.MachineInfo.* metrics, sourced from /etc/machine-info
 if [ -e /etc/machine-info ]; then
     MACHINE_INFO_BACKUP="$(mktemp)"