[metrics] Add basic system wide and per unit metrics

This commit adds some basic metrics and integration tests.

System wide metrics:
- units_by_type_total: target/device/automount etc.
- units_by_state_total: active/reloading/inactive etc.

Two per unit metrics which shows the current state of a unit:
- unit_active_state
- unit_load_state

A metric for service state:
- nrestarts

Here are some sample outputs:

units_by_type_total:

{
        "name" : "io.systemd.Manager.units_by_type_total",
        "value" : 52,
        "fields" : {
                "type" : "target"
        }
}
{
        "name" : "io.systemd.Manager.units_by_type_total",
        "value" : 82,
        "fields" : {
                "type" : "device"
        }
}
{
        "name" : "io.systemd.Manager.units_by_type_total",
        "value" : 2,
        "fields" : {
                "type" : "automount"
        }
}

units_by_state_total:

{
        "name" : "io.systemd.Manager.units_by_state_total",
        "value" : 216,
        "fields" : {
                "state" : "active"
        }
}
{
        "name" : "io.systemd.Manager.units_by_state_total",
        "value" : 0,
        "fields" : {
                "state" : "reloading"
        }
}
{
        "name" : "io.systemd.Manager.units_by_state_total",
        "value" : 120,
        "fields" : {
                "state" : "inactive"
        }
}

unit_active_state:

{
        "name" : "io.systemd.Manager.unit_active_state",
        "object" : "multi-user.target",
        "value" : "active"
}
{
        "name" : "io.systemd.Manager.unit_active_state",
        "object" : "systemd-sysusers.service",
        "value" : "inactive"
}

unit_load_state:

{
        "name" : "io.systemd.Manager.unit_load_state",
        "object" : "multi-user.target",
        "value" : "loaded"
}

nrestarts:

{
        "name" : "io.systemd.Manager.nrestarts",
        "object" : "user@0.service",
        "value" : 0
}
{
        "name" : "io.systemd.Manager.nrestarts",
        "object" : "user-runtime-dir@0.service",
        "value" : 0
}
This commit is contained in:
Yaping Li
2026-02-02 08:03:56 -08:00
parent fd73cd6c91
commit bb1ef2edf7
6 changed files with 239 additions and 3 deletions
+2
View File
@@ -469,6 +469,8 @@ typedef struct Manager {
* systemd-oomd to report changes in ManagedOOM settings (systemd client - oomd server). */
sd_varlink *managed_oom_varlink;
sd_varlink_server *metrics_varlink_server;
/* Reference to RestrictFileSystems= BPF program */
struct restrict_fs_bpf *restrict_fs;
+1
View File
@@ -69,6 +69,7 @@ libcore_sources = files(
'varlink-dynamic-user.c',
'varlink-execute.c',
'varlink-manager.c',
'varlink-metrics.c',
'varlink-unit.c',
)
+173
View File
@@ -0,0 +1,173 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "hashmap.h"
#include "manager.h"
#include "metrics.h"
#include "service.h"
#include "unit-def.h"
#include "unit.h"
#include "varlink-metrics.h"
static int unit_active_state_build_json(MetricFamilyContext *context, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
Unit *unit;
char *key;
int r;
assert(context);
HASHMAP_FOREACH_KEY(unit, key, manager->units) {
/* ignore aliases */
if (key != unit->id)
continue;
r = metric_build_send_string(
context,
unit->id,
unit_active_state_to_string(unit_active_state(unit)),
/* field_pairs= */ NULL);
if (r < 0)
return r;
}
return 0;
}
static int unit_load_state_build_json(MetricFamilyContext *context, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
Unit *unit;
char *key;
int r;
assert(context);
HASHMAP_FOREACH_KEY(unit, key, manager->units) {
/* ignore aliases */
if (key != unit->id)
continue;
r = metric_build_send_string(
context,
unit->id,
unit_load_state_to_string(unit->load_state),
/* field_pairs= */ NULL);
if (r < 0)
return r;
}
return 0;
}
static int nrestarts_build_json(MetricFamilyContext *context, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
int r;
assert(context);
LIST_FOREACH(units_by_type, unit, manager->units_by_type[UNIT_SERVICE]) {
r = metric_build_send_unsigned(
context, unit->id, SERVICE(unit)->n_restarts, /* field_pairs= */ NULL);
if (r < 0)
return r;
}
return 0;
}
static int units_by_type_total_build_json(MetricFamilyContext *context, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
int r;
assert(context);
for (UnitType type = 0; type < _UNIT_TYPE_MAX; type++) {
uint64_t counter = 0;
LIST_FOREACH(units_by_type, _u, manager->units_by_type[type])
counter++;
r = metric_build_send_unsigned(
context,
/* object= */ NULL,
counter,
STRV_MAKE("type", unit_type_to_string(type)));
if (r < 0)
return r;
}
return 0;
}
static int units_by_state_total_build_json(MetricFamilyContext *context, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
UnitActiveState counters[_UNIT_ACTIVE_STATE_MAX] = {};
Unit *unit;
char *key;
int r;
assert(context);
/* TODO need a rework probably with state counter */
HASHMAP_FOREACH_KEY(unit, key, manager->units) {
/* ignore aliases */
if (key != unit->id)
continue;
counters[unit_active_state(unit)]++;
}
for (UnitActiveState state = 0; state < _UNIT_ACTIVE_STATE_MAX; state++) {
r = metric_build_send_unsigned(
context,
/* object= */ NULL,
counters[state],
STRV_MAKE("state", unit_active_state_to_string(state)));
if (r < 0)
return r;
}
return 0;
}
const MetricFamily metric_family_table[] = {
// Keep metrics ordered alphabetically
{
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "nrestarts",
.description = "Per unit metric: number of restarts",
.type = METRIC_FAMILY_TYPE_COUNTER,
.generate_cb = nrestarts_build_json,
},
{
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_active_state",
.description = "Per unit metric: active state",
.type = METRIC_FAMILY_TYPE_STRING,
.generate_cb = unit_active_state_build_json,
},
{
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_load_state",
.description = "Per unit metric: load state",
.type = METRIC_FAMILY_TYPE_STRING,
.generate_cb = unit_load_state_build_json,
},
{
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "units_by_state_total",
.description = "Total number of units of different state",
.type = METRIC_FAMILY_TYPE_GAUGE,
.generate_cb = units_by_state_total_build_json,
},
{
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "units_by_type_total",
.description = "Total number of units of different types",
.type = METRIC_FAMILY_TYPE_GAUGE,
.generate_cb = units_by_type_total_build_json,
},
{}
};
int vl_method_describe(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(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);
}
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-varlink.h"
#include "sd-json.h"
#define METRIC_IO_SYSTEMD_MANAGER_PREFIX "io.systemd.Manager."
int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+41 -3
View File
@@ -5,6 +5,7 @@
#include "constants.h"
#include "errno-util.h"
#include "manager.h"
#include "metrics.h"
#include "path-util.h"
#include "pidref.h"
#include "string-util.h"
@@ -18,6 +19,7 @@
#include "varlink-io.systemd.UserDatabase.h"
#include "varlink-io.systemd.service.h"
#include "varlink-manager.h"
#include "varlink-metrics.h"
#include "varlink-serialize.h"
#include "varlink-unit.h"
#include "varlink-util.h"
@@ -423,8 +425,26 @@ int manager_setup_varlink_server(Manager *m) {
return 1;
}
static int manager_setup_varlink_metrics_server(Manager *m) {
sd_varlink_server_flags_t flags = SD_VARLINK_SERVER_INHERIT_USERDATA;
int r;
assert(m);
if (MANAGER_IS_SYSTEM(m))
flags |= SD_VARLINK_SERVER_ACCOUNT_UID;
r = metrics_setup_varlink_server(
&m->metrics_varlink_server, flags, m->event, vl_method_list, vl_method_describe, m);
if (r < 0)
return r;
return 0;
}
static int manager_varlink_init_system(Manager *m) {
int r;
_cleanup_free_ char *metrics_address = NULL;
assert(m);
@@ -433,16 +453,29 @@ static int manager_varlink_init_system(Manager *m) {
return log_error_errno(r, "Failed to set up varlink server: %m");
bool fresh = r > 0;
r = manager_setup_varlink_metrics_server(m);
if (r < 0)
return log_error_errno(r, "Failed to set up metrics varlink server: %m");
bool metrics_fresh = r > 0;
r = runtime_directory_generic(m->runtime_scope, "systemd/report/io.systemd.Manager", &metrics_address);
if (r < 0)
return r;
if (!MANAGER_IS_TEST_RUN(m)) {
FOREACH_STRING(address,
"/run/systemd/userdb/io.systemd.DynamicUser",
VARLINK_PATH_MANAGED_OOM_SYSTEM,
"/run/systemd/io.systemd.Manager") {
"/run/systemd/io.systemd.Manager",
metrics_address) {
sd_varlink_server *server = streq(address, metrics_address) ? m->metrics_varlink_server : m->varlink_server;
fresh = streq(address, metrics_address) ? metrics_fresh : fresh;
/* We might have got sockets through deserialization. Do not bind to them twice. */
if (!fresh && varlink_server_contains_socket(m->varlink_server, address))
if (!fresh && varlink_server_contains_socket(server, address))
continue;
r = sd_varlink_server_listen_address(m->varlink_server, address, 0666 | SD_VARLINK_SERVER_MODE_MKDIR_0755);
r = sd_varlink_server_listen_address(server, address, 0666 | SD_VARLINK_SERVER_MODE_MKDIR_0755);
if (r < 0)
return log_error_errno(r, "Failed to bind to varlink socket '%s': %m", address);
}
@@ -479,6 +512,10 @@ static int manager_varlink_init_user(Manager *m) {
return log_error_errno(r, "Failed to bind to varlink socket '%s': %m", address);
}
r = manager_setup_varlink_metrics_server(m);
if (r < 0)
return log_error_errno(r, "Failed to set up metrics varlink server: %m");
return manager_varlink_managed_oom_connect(m);
}
@@ -497,6 +534,7 @@ void manager_varlink_done(Manager *m) {
m->varlink_server = sd_varlink_server_unref(m->varlink_server);
m->managed_oom_varlink = sd_varlink_close_unref(m->managed_oom_varlink);
m->metrics_varlink_server = sd_varlink_server_unref(m->metrics_varlink_server);
}
void manager_varlink_send_pending_reload_message(Manager *m) {
@@ -210,6 +210,18 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup":
invocation_id="$(systemctl show -P InvocationID systemd-journald.service)"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}"
# test io.systemd.Metrics
varlinkctl info /run/systemd/report/io.systemd.Manager
varlinkctl list-methods /run/systemd/report/io.systemd.Manager
varlinkctl list-methods -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq .
varlinkctl introspect /run/systemd/report/io.systemd.Manager
varlinkctl introspect -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq .
varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List {}
varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.Describe {}
# test io.systemd.Manager in user manager
testuser_uid=$(id -u testuser)
systemd-run --wait --pipe --user --machine testuser@ \