]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add kernel/userspace/finish boot timestamps to io.systemd.Manager metrics
authorMichael Vogt <michael@amutable.com>
Tue, 26 May 2026 13:52:40 +0000 (15:52 +0200)
committerMichael Vogt <michael@amutable.com>
Sat, 20 Jun 2026 08:37:47 +0000 (10:37 +0200)
This commit adds the boot timeline (MANAGER_TIMESTAMP_KERNEL/USERSPACE/FINISH) as
metrics. The kernel CLOCK_MONOTONIC value is 0 by definition, so only its
.Realtime is reported. For userspace and finish report both .Realtime and
.Monotonic. The naming follows D-Bus.

src/core/varlink-metrics.c
test/units/TEST-74-AUX-UTILS.report.sh

index 9992264c788ac265da735628dd59e75dbda3b063..0e6bfa961890da40e42b1eb3ad694146265c25e3 100644 (file)
@@ -7,6 +7,7 @@
 #include "manager.h"
 #include "metrics.h"
 #include "service.h"
+#include "string-util.h"
 #include "unit-def.h"
 #include "unit.h"
 #include "varlink-metrics.h"
@@ -97,6 +98,59 @@ static int version_build_json(const MetricFamily *mf, sd_varlink *vl, void *user
                         /* fields= */ NULL);
 }
 
+static int boot_timestamp_build_json(
+                const MetricFamily *mf,
+                sd_varlink *vl,
+                const dual_timestamp *t,
+                bool with_monotonic) {
+
+        int r;
+
+        assert(mf && mf->name);
+        assert(vl);
+        assert(t);
+
+        if (timestamp_is_set(t->realtime)) {
+                r = metric_build_send_unsigned(
+                                mf,  /* the .Realtime metric family entry */
+                                vl,
+                                /* object= */ NULL,
+                                t->realtime,
+                                /* fields= */ NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        if (with_monotonic && timestamp_is_set(t->monotonic)) {
+                assert(endswith(mf[1].name, ".Monotonic"));
+                r = metric_build_send_unsigned(
+                                mf + 1,  /* the .Monotonic sibling is the next entry */
+                                vl,
+                                /* object= */ NULL,
+                                t->monotonic,
+                                /* fields= */ NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int kernel_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        Manager *manager = ASSERT_PTR(userdata);
+        return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_KERNEL], /* with_monotonic= */ false);
+}
+
+static int userspace_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        Manager *manager = ASSERT_PTR(userdata);
+        return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_USERSPACE], /* with_monotonic= */ true);
+}
+
+static int finish_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        Manager *manager = ASSERT_PTR(userdata);
+        return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_FINISH], /* with_monotonic= */ true);
+}
+
 static int state_change_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
         Manager *manager = ASSERT_PTR(userdata);
         Unit *unit;
@@ -403,6 +457,19 @@ static const MetricFamily metric_family_table[] = {
                 .type = METRIC_FAMILY_TYPE_GAUGE,
                 .generate = active_timestamp_build_json,
         },
+        {
+                .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "FinishTimestamp.Realtime",
+                .description = "CLOCK_REALTIME microseconds at which userspace finished booting",
+                .type = METRIC_FAMILY_TYPE_GAUGE,
+                .generate = finish_timestamp_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "FinishTimestamp.Monotonic",
+                .description = "CLOCK_MONOTONIC microseconds at which userspace finished booting",
+                .type = METRIC_FAMILY_TYPE_GAUGE,
+                .generate = NULL,
+        },
+        /* Keep those ↑ in sync with finish_timestamp_build_json(). */
         {
                 .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "InactiveExitTimestamp",
                 .description = "Per unit metric: timestamp when the unit last exited the inactive state in microseconds; 0 indicates the transition has not occurred",
@@ -415,6 +482,12 @@ static const MetricFamily metric_family_table[] = {
                 .type = METRIC_FAMILY_TYPE_GAUGE,
                 .generate = jobs_queued_build_json,
         },
+        {
+                .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "KernelTimestamp.Realtime",
+                .description = "CLOCK_REALTIME microseconds at which the kernel started (CLOCK_MONOTONIC == 0)",
+                .type = METRIC_FAMILY_TYPE_GAUGE,
+                .generate = kernel_timestamp_build_json,
+        },
         {
                 .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "NRestarts",
                 .description = "Per unit metric: number of restarts",
@@ -481,6 +554,19 @@ static const MetricFamily metric_family_table[] = {
                 .type = METRIC_FAMILY_TYPE_GAUGE,
                 .generate = units_total_build_json,
         },
+        {
+                .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UserspaceTimestamp.Realtime",
+                .description = "CLOCK_REALTIME microseconds at which userspace was reached",
+                .type = METRIC_FAMILY_TYPE_GAUGE,
+                .generate = userspace_timestamp_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UserspaceTimestamp.Monotonic",
+                .description = "CLOCK_MONOTONIC microseconds at which userspace was reached",
+                .type = METRIC_FAMILY_TYPE_GAUGE,
+                .generate = NULL,
+        },
+        /* Keep those ↑ in sync with userspace_timestamp_build_json(). */
         {
                 .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "Version",
                 .description = "Version of systemd",
index e2d94df75b84997ff5ddc67ceec559af61c2bd80..46f61b3b266f7e03644350c7c05c05cca3a975c7 100755 (executable)
@@ -85,6 +85,19 @@ metrics_version="$(varlinkctl call --more /run/systemd/report/io.systemd.Manager
 [ -n "$metrics_version" ]
 systemctl --version | grep -F "($metrics_version)" >/dev/null
 
+# Boot timeline timestamps. The kernel CLOCK_MONOTONIC is 0 by definition, so only its realtime is
+# reported; userspace reports both realtime and monotonic. We don't check FinishTimestamp here:
+# the test runs as a service, so manager_check_finished() typically hasn't fired yet and the
+# timestamp is unset (the metric is then suppressed). Likewise KernelTimestamp is cleared when
+# systemd runs inside a container (see main.c), so the metric is suppressed there too.
+manager_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List {})"
+metric_value() { echo "$manager_metrics" | jq --seq -r "select(.name == \"io.systemd.Manager.$1\") | .value | tostring"; }
+if ! systemd-detect-virt --quiet --container; then
+    [ "$(metric_value KernelTimestamp.Realtime)" -gt 0 ]
+fi
+[ "$(metric_value UserspaceTimestamp.Realtime)" -gt 0 ]
+[ "$(metric_value UserspaceTimestamp.Monotonic)" -gt 0 ]
+
 # test io.systemd.Basic.MachineInfo.* metrics, sourced from /etc/machine-info
 if [ -e /etc/machine-info ]; then
     MACHINE_INFO_BACKUP="$(mktemp)"